Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Столкновение объектов. Как правильно реализовать?


Автор: w1zard350 6.5.2009, 09:24
Здравствуйте!

Пишу игру в танчики. Borland C++ 3.1. 

Суть в том, что нужно реализовать столкновения объектов. Пока что объектами будем считать "стены" и "танки". 

У меня есть массив "стен". Элементом массива является структура, которая содержит координаты двух точек, образующих прямоугольник. 

Я придумал такой алгоритм:
Определяем в каком направлении движется танк. Т.к. координаты танка представлены одной точкой (центр танка), то после этого я просчитываю координаты "головы", "хвоста" и боков танка. Потом в цикле просматриваю все объекты, которые есть (пока что только стены). Внутри цикла я проверяю или "голова" танка входит окресность стены. Если входит - двигаем танк максимально близко к стенке и прекращаем движение. Иначе - разрешаем движение.

Вот код для движения вверх:
Код

int front,leftSide,rightSide,upSide,downSide,flag=1,i;
    leftSide = tank->x - w/2;
    rightSide = tank->x + w/2;
    upSide = tank->y - h/2;
    downSide = tank->y + h/2;
 
switch(direction)
    {
        // up
        case 1:
            front = tank->y - h/2;
            for (i = 0; i < countObjects && flag; i++)
            {
                if((front - tank->v <= (walls[i].y2)) &&
                   (front - tank->v > (walls[i].y1)) &&
                   (leftSide < (walls[i].x2)) &&
                   (rightSide > (walls[i].x1))
                  )
                {
                    flag = 0;
                    if (front != (walls[i].y2 + distance))
                    {
                        printTank (tank->x, tank->y, 0);
                        printTank (tank->x, walls[i].y2 + h/2 + distance, 1);
                        tank->y = walls[i].y2 + h/2 + distance;
                    }
 
                    break;
                }
            }
            if (flag)
            {
                printTank (tank->x, tank->y, 0);
                printTank (tank->x, tank->y - tank->v, 1);
                tank->y -= tank->v;
            }
    }
            break;

как видите, я просматриваю объекты только "стены", и то, их количество определено константой, т.е. при добавлении нового объекта я увеличиваю константу. Это очень плохо.

Собственно, вопрос:
Как мне организовать подобную проверку, только для всех объектов - стен и танков (потом еще и снарядов). Т.е. мне как-то нужно занести в один список три разных вида массива: массив структур стен, массив структур танков и массив структур снарядов.

Я понятия не имею, каким образом объединить все эти массивы и потом еще организовать хорошую проверку на столкновение. 

Сейчас смотрю в сторону связных списков. Я эту тему еще не изучил, поэтому не уверен, в ту ли сторону я собрался. А связный список мне пригодится хотя бы потому, что я точно не могу знать количество объектов в игре.

Дайте пожалуйста советы по теме, правильный ли у меня подход к программированию столкновений? Нужно ли использовать списки?

Надеюсь на вас, профессионалы 

Автор: Static 6.5.2009, 11:09
Мое видение:
Унаследовать все объекты от общего предка и поместить их в один вектор. Это позволит перебирать все объекты одним циклом.
Я бы наверно делал так
Псевдокод
Код

struct Base
{
int id; // например идентификатор. Или еще чего-нить общее можно сюда запихать.
}

struct tank : Base
{
int x,y;
//еще чего-нить
}

struct bullet : Base
{
int x,y;
char direction; //куда летим-то
//еще че-нить
}

struct MyTank : tank
{
int hp;
//еще че-нить
}

struct Wall : Base
{
coords;
}

vector<Base> objects; // все объекты
void PutAllObjectTogether(&objects); // существующие объекты запихиваем в вектор

vector<Base> movable;
void PutMovableObjectTogether(&objects); //это только те, кто умеет шевелиться

int Action = GetActionFromPlyer(); // смотрим, чего хочет юзер
switch (Action)
{
case ACTION_FIRE: //дефайн какой-нить
       Fire(My_tank->getCoords(), My_tank->getDirection()); // ну типа стреляем. В функции добавляем в спиок новый объект-снаряд
       break;
case ACTION_MOVE:
       if( VerifyObstacle(My_tank->getDirection()) ) //В этой функции с помощью for_each (например) обходим все objects и проверяем их координаты. Функция возвращает bool (например)
               Move(My_tank); //двигаемся
      break;
default:
       DoNothing();
}

//тут при помощи того же for_each обходим наших movable генерим им движение и повторяем процедуру проверки для них. 



ну это очень примерно и "на коленке". Для пущей кошерности можно вместо структур классы сделать. И конструкторы им качественные с деструкторами smile, которые позволят правильно  добавлять/удалять объекты из списков.

Автор: math64 6.5.2009, 12:30
Танк и снаряд тоже можно считать прямоугольниками.
Для определения времени столкновения двух движущихся объектов перейти в систему координат связанную с одним из них.
Код

class Base {
protected:
double x, y; // координаты центра
double width, length; // ширина и длина прямоугольника
double angle; // угол поворота
double vx, vy; // скорость
Base* next; // Для включения в список
//Base* prev; // Если нужен двунаправленный список
public:
   virtual bool Intersects(Base& base2) = 0; // Столкнулись  ли два объекта?
   virtual double WillIntersectAfter(Base& base2) =0; // Вычислить время столкновения
   ...
};
class Wall : public Base {
   ...
};
class Tank : public Base {
   ...
};
class Bullet : public Base {
   ...
};
// В BC 3.1 нет vector<>, list<> создаём свой
class List {
protected:
   Base* first;
   Base* last;
public:
   ...
};

Автор: w1zard350 24.5.2009, 10:07
Спасибо огромное, общий смысл ясен! Благодаря вашим примерам продвинулся уже дальше.

Сейчас перешел к столкновениям пуль. И возникла проблема.

Написал функцию fire(). Она закидывает в список новый элемент со всеми нужными параметрами (координаты, скорость,направление). Потом в главном цикле написал функцию moveBullets(), которая просматривает список пуль и двигает их на шаг в зависимости от скорости. 

Проблема возникает при столкновении пули с другим объектом. Мои мысли на эту тему: когда столкнулись пуля и объект, определить с каким типом объекта пуля столкнулась (танк, стена). Если со стеной, просто затереть пулю. Если с танком, то нужно достать из списка все его данные и обнулить их на некоторое время, потом танк вновь входит в игру. Проблема возникла детская: как мне получить указатель на объект, с которым столкнулась пуля? 

Внутри функции проверки столкновений, пытаюсь вернуть через параметр указатель на этот объект. Вот кусок кода:
Код

int verifyObstacle(object*pObject, int direction, int v, object*result)
{
    current = first;

    while (current->next != NULL)
    {
    switch(direction)
    {
        case DIRECTION_UP:
        if(/*если столкнулись*/)
          {
                        result = current;
                        return 0;
                  }

После работы функции указатель result уже никуда не указывает, как решить проблему пока не знаю, думаю. Надеюсь вы сможете указать на ошибку алгоритма.


Автор: andrew_121 24.5.2009, 11:18
Я вот заметил странную закономерность. На БС-3.1 пишут только студики. И это похоже он...

Автор: Dmi3ev 24.5.2009, 11:34
 smile 
Цитата

Я вот заметил странную закономерность. На БС-3.1 пишут только студики. И это похоже он... 

в ВУЗах идет его пропаганда, а точнее преподы, которые учат, не знают о существовании более поздних версий, а ще там в консоли рисовать можно, а еще там прикольно прогать, мне нравилось, я даже на форуме спрашивал, есть ли что-то поновее, но при этом с таким же ср...ым интерфейсом, мне ваще нравится, не знаю почему )))

Автор: andrew_121 24.5.2009, 11:49
Цитата(Dmi3ev @  24.5.2009,  11:34 Найти цитируемый пост)
я даже на форуме спрашивал, есть ли что-то поновее, но при этом с таким же ср...ым интерфейсом, мне ваще нравится, не знаю почему )))

****ист!  smile 

Автор: w1zard350 24.5.2009, 12:40
С пулями проблема решилась, ответ в теме: 
http://forum.vingrad.ru/forum/topic-260485/kw-указатель-локальные-переменные-возврат.html

Продолжаем разработку smile

PS: 
Не по теме:
Цитата

Я вот заметил странную закономерность. На БС-3.1 пишут только студики. И это похоже он...

Вы совершенно правы, и Dmi3ev насчет вузов тоже прав smile Нас обязывают там писать, такое требование и ничего тут не поделаешь. Вообще это курсовая у меня такая smile Не охота мне ее заказывать у кого-то, т.к. сам программированием очень увлекаюсь smile

Автор: andrew_121 24.5.2009, 12:42
Цитата(w1zard350 @  24.5.2009,  12:40 Найти цитируемый пост)
Не охота мне ее заказывать у кого-то, т.к. сам программированием очень увлекаюсь

Похвально!

Автор: math64 25.5.2009, 08:14
Насчёт столкновений: Для определения типа объекта, с которым произошло столкновение используется виртульная функция:
Код

class Base {
...
public:
virtual void Damaged(Base& other) = 0;// Вычисляем повреждения нанесённые объекту
...
}

...
Base* base1;
Base* base2;
...
// Определение стокновений
if (base1->Inresects(*base2) { // Если объекты столкнулись
   base1->Damaged(*base2); // Вычисляем повреждения нанесённые объекту base1
   base2->Damaged(*base1); // Вычисляем повреждения нанесённые объекту base2
}

Автор: w1zard350 8.6.2009, 10:44
Вот написал я наконец танчики, но меня убивает проблема глюков. Глюки проявляются в том, что во время игры, резко все танки начинают быстро двигаться, а потом игра вылетает, или может быть ситуация, когда долго ездишь-ездишь, а потом резко все зависает и не работают клавиши. 

Я вообще не могу понять в какую сторону мне копать, что менять? Я проверял, что когда я выйду в винду (Alt-Enter) и в диспетчере смотрю быстродействие, то загрузка ЦП на 100%. Ну да, игра грузит, но... если такая слабая игра под дос грузит, как же делают современные игры?

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

PS: В аттач приложил exe-шник. Если есть желание, посмотрите пожалуйста и выскажите свои предположения.
Я тут убрал движение ботов, т.к. и без них игра глючит. управление wasd и тильда для стрельбы.
Выкладывать код пока не стал, т.к. не уверен, что у кого-то будет желание разбирать тысячи строк кода. Но если все таки нужен - я выложу.

Автор: fry 8.6.2009, 14:30
Я как-то ище в школе делал часы( правда на Pascal ). Так вот они грузили мало не покажется, а все из-за графического вывода, т.е. смысл в том, что стандартные средства визуализации из компиллятора были жудко тормозными. Думаю если проблеема в этом, то переписав графический вывод под, скажем, OpenGL ты разгрузишь проц т.к. всю визуализацию будет делать твоя видюха. Если этого будет недостаточно, то думаю надо копать в самих принципах реализации игрушки. Всетаки 100% это слишком много. Точнее сказать не могу, нужны коды (а еще желание).

Автор: w1zard350 8.6.2009, 14:35
Переписывать графический вывод явно не буду. Это ведь обычная курсовая. А Вы пробовали у себя запускать игрушку? У Вас она вылетает? Грузит проц?

Автор: zim22 8.6.2009, 14:49
Цитата(w1zard350 @  8.6.2009,  14:35 Найти цитируемый пост)
 А Вы пробовали у себя запускать игрушку? У Вас она вылетает? Грузит проц?

она вообще не запускается.
сначала ругалась на отстутствие файла EGAVGA.BGI
потом ввиду того, что у меня Vista, - отказалась запускаться в полноэкранном режиме. я знаю, что приплясывая с бубном можно добиться запуска - но оно мне не надо smile

Автор: w1zard350 8.6.2009, 15:51
Блин, а я думал,  что после того как откомпилировал прогу, то все пути к драйверу и т.п. уже не важны... вот блин. То есть, если я хочу кому-то дать игрушку, то у него обязательно должен быть установлен BC 3.1 и путь к драйверу точно такой же как и у меня? Наивный я был... А как делают тогда современные игры? Ты устанавливаешь вместе с игрой все драйвера нужные для работы, оно где-то у себя сохраняет... да получается что так. Эххх... В общем, пойду копать дальше сам, а если у кого-то есть желание повозиться, то все таки попробуйте запустить у себя. Если нужен исходный код - стучите в личку.

Автор: xvr 8.6.2009, 16:32
Цитата(w1zard350 @ 8.6.2009,  15:51)
То есть, если я хочу кому-то дать игрушку, то у него обязательно должен быть установлен BC 3.1 и путь к драйверу точно такой же как и у меня? 

У него должен быть драйвер по путям, прописанным в программе. Весь BC3.1 не нужен
Цитата

А как делают тогда современные игры? 
Современные игры на BC 3.1 не пишут
Цитата

Ты устанавливаешь вместе с игрой все драйвера нужные для работы, 
В 'современных играх' 'драйвера нужные для работы' - это DirectX, и его иногда таскают вместе с игрой (в отдельной инсталяшке)

Автор: w1zard350 8.6.2009, 17:19
Спасибо за разъяснение smile Я действительно этого всего не знал. Вот только сейчас столкнулся со всеми этими делами smile

Автор: Курсант 8.6.2009, 17:39
Предложение есть, только не надо меня пинать с приговором "болтун, где исходники?" и т.п... От идеи до модели надо потратить прилично времени, если оно у меня будет - напишу. А пока идея такова.

Исходные данные - скорости снарядов и всех объектов, координаты их точек. Цель - определить минимальное время столкновения двух объектов, и до этого времени ничего не трогать. Как только кинематика объектов поменялась (танк заглох изменил направление движения, возник новый объект с кинетическими характеристиками), то происходит пересчет минимального времени следующего столкновения двух объектов, и до этого времени объекты катаются как ни в чем не бывало...

В качестве объектов можно использовать структуры, содержащие в себе точки прямоугольников, и векторы скорости. Все объекты загнать в двусвязный список и для каждой пары объектов рассчитывать время до столкновения, если оно минимальное из всех прежних - сохранять его в minTime, сохранять индексы объектов двусвязного списка в переменные. Вот и все. После того как minTime найдено, проверять не столкнулся ли кто с кем - вовсе не обязательно до тех пор пока не наступило minTime. А после того как оно наступило - нужно отрисовывать взрывы. И так на каждое изменение состояния каждого объекта нужно производить персчет minTime для всей обстановки.

Уф, щас попробую сделать, если нужно, но сразу говорю - я только учусь, времени займет много, поэтому если кто из более опытных товарищей сделает - будет гут, я сам посмотрю...

Автор: Курсант 8.6.2009, 20:11
Вот получилось кой чего... Функция, которая возвращает время до столкновения двух объектов, заданных координатами диаметрально-противоположных углов и векторами скоростей. Объекты - записи. Функция от записей, переданных по ссылке.

Отладка минимальная была, но по логике должна работать, на одном варианте проверял. В случае если столкновения не будет возвращает 100000 smile Простите меня smile

Код

#include <iostream>
using namespace std;

const char Coords[2] = {'x','y'};
const char CornNames[2][12] = {"Left Bottom", "Right Top\0 "};

struct Unit
{
    float itsCoords[2][2]; // 1-й индекс - номер угла (1-й угол танка, или диаметрально-противоположный второй), 2-й индекс - координата x или y
    float itsVel [2]; // itsVel[0] - проекция скорости на ось x, itsVel[1] - проекция скорости на ось y
};

float TimeToColl (Unit& Unit1, Unit& Unit2)
{
    float minTime = 100000; // кто нить подскажите как получить максимальное значения для типа данных??? :)
    float currTime;
    for (int OxOy=0; OxOy<2; OxOy++) // индекс координатной оси, по которой рассчит. время столкн...
    {

        if (Unit1.itsVel[OxOy] == Unit2.itsVel[OxOy]) // если проекции скоростей равны...
        {
             currTime = 100000; // то ждать столкновения не стоит...
        }
        else // иначе - находим время столкновения...
        {
        for (int T1Corn=0; T1Corn<2; T1Corn++) // индекс проверяемого угла для танка 1...
            for (int T2Corn=0; T2Corn<2; T2Corn++) // индекс проверяемого угла для танка 2...
            {
                currTime = (Unit1.itsCoords[T1Corn][OxOy] - Unit2.itsCoords[T2Corn][OxOy])/(Unit2.itsVel[OxOy]-Unit1.itsVel[OxOy]);
                // следующий if проверяет, через время столкновения по оси x будет ли совпадение по оси y и наоборот, в зависимости
                // от OxOy, которая и определяет текущую просчитываемую уось...
                if ((Unit1.itsCoords[T1Corn][1-OxOy]+Unit1.itsVel[1-OxOy]*currTime-Unit2.itsCoords[T2Corn][1-OxOy]-Unit2.itsVel[1-OxOy]*currTime)*
                     (Unit1.itsCoords[1-T1Corn][1-OxOy]+Unit1.itsVel[1-OxOy]*currTime-Unit2.itsCoords[T2Corn][1-OxOy]-Unit2.itsVel[1-OxOy]*currTime) 
                        > 0)
                {
                     currTime = 100000; // если произведение разностей координат углов первого танка и текущего угла второго > 0, то угол второго вне объекта, все пропало...
                }
            }
            if ((currTime < minTime)&&(currTime>=0))
             minTime = currTime;
        }

    }
    return minTime;
}

int main()
{
    Unit Tank1;
    Unit Tank2;
    for (int Corn=0; Corn<2; Corn++)
    {
        cout << "Enter the Tank1 " << CornNames[Corn] << " coordinates x y: ";
        cin >> Tank1.itsCoords[Corn][0] >> Tank1.itsCoords[Corn][1];
    }
    cout << "Enter the Tank1 Velocity vector proections Vx Vy: ";
    cin >> Tank1.itsVel[0] >> Tank1.itsVel[1];
    for (int Corn=0; Corn<2; Corn++)
    {
        cout << "Enter the Tank2 " << CornNames[Corn] << " coordinates x y: ";
        cin >> Tank2.itsCoords[Corn][0] >> Tank2.itsCoords[Corn][1];
    }
    cout << "Enter the Tank2 Velocity vector proections Vx Vy: ";
    cin >> Tank2.itsVel[0] >> Tank2.itsVel[1];
    float TimeToX = TimeToColl(Tank1, Tank2);
    cout << "Time to collision is: " << TimeToX << endl;
    cin >> TimeToX;
    return 0;
}


Добавлено @ 20:16
Мда, если будете использовать такую функцию, то есть смысл окружить поле боя стенками, чтобы было куда врезаться в любом случае smile

Автор: Курсант 8.6.2009, 22:08
А вот тебе классы для твоих юнитов и массива юнитов, может чем полезно окажется... Списков не делал - использовал массив указателей на юниты, размещенный в динамической памяти. Там еще небольш демонстр. программка есть... С доступом к переменными я конечно погорячился, а так вроде ничего... С освобождением памяти не дружу, поэтому некоторые delete у меня вызывали ошибку, я их закомментил. Если предыдущую функцию к этим классам адаптировать - почти все что ты хотел....

Код

#include <iostream>
using namespace std;

class Unit
{
public:
    Unit (float Lx, float By, float Rx, float Ty, float Vx, float Vy);
    float itsCoords[2][2];
    float itsVel[2];
};

Unit::Unit (float Lx, float By, float Rx, float Ty, float Vx, float Vy)
{
    itsCoords[0][0]=Lx;
    itsCoords[0][1]=By;
    itsCoords[1][0]=Rx;
    itsCoords[1][1]=Ty;
    itsVel[0]=Vx;
    itsVel[1]=Vy;
}

class UnitsArray
{
public:
    UnitsArray():itsCount(0), itsUnits(0) {};
    UnitsArray(Unit&);
    UnitsArray(UnitsArray& From);
    void AddUnit (Unit&);
    void DeleteUnit (int Index);
    ~UnitsArray();
    Unit& operator[] (int theIndex) { return *itsUnits[theIndex];};
    int itsCount;
    Unit** itsUnits;
};

UnitsArray::UnitsArray(Unit& theUnit)
{
    itsUnits = new Unit* [1];
    itsCount = 1;
    itsUnits[0]= &theUnit;
}

UnitsArray::UnitsArray(UnitsArray& From)
{
    itsUnits = new Unit* [From.itsCount];
    for (int i=0; i<From.itsCount; i++)
        itsUnits[i]=From.itsUnits[i];
}

UnitsArray::~UnitsArray()
{
    for (int i=0; i<itsCount; i++)
    {
        cout << i << " is deleted..." << endl;
        //delete itsUnits[i];
    }
    delete itsUnits;
}

void UnitsArray::AddUnit(Unit& theUnit)
{
    Unit** temp;
    temp = new Unit* [itsCount+1];
    for (int i=0; i<itsCount; i++)
        temp[i] = itsUnits[i];
    temp [itsCount] = &theUnit;
    itsCount++;
    delete itsUnits;
    itsUnits = temp;
}

void UnitsArray::DeleteUnit(int Index)
{
    if (Index < (itsCount-1))
    {
        Unit** temp = new Unit* [itsCount-1];
        for (int i=0; i<Index; i++)
            temp[i] = itsUnits[i];
        for (int i=Index+1; i<itsCount ; i++)
            temp[i-1] = itsUnits[i];
        //delete itsUnits[Index];
        itsCount--;
        delete itsUnits;
        itsUnits = temp;
    }
}

int main()
{
    UnitsArray theArr; // объявляем массив юнитов, пока он пуст...
    Unit Tank1 (10, 10, 20, 20, 40, 40); // объявляем юниты Танк1 и Танк2...
    Unit Tank2 (40, 40, 50, 50, 30, 30);
    theArr.AddUnit (Tank1); // добавляем в массив юнитов Танк1;
    theArr.AddUnit (Tank2); // и Танк 2;
    theArr.DeleteUnit (0); // удаляем нулевой танк...
    cout << "Zero Unit in array is: " << theArr[0].itsVel[0] << " " << theArr[0].itsVel[1] << endl; // доступ к Юнитам массива.
    // то есть theArr[0] возвращает по ссылке тип Юнит, объект нах-ся в массиве на нул. позиции...
    cout << "Ok";
    int x;
    cin >> x;
}

Автор: Курсант 8.6.2009, 22:25
Мда, темы надо прочитывать целиком.. Я и не заметил что танчики у него уже написаны... Сорри... Убирать не буду, может пригодится...

Автор: Dmi3ev 9.6.2009, 02:43
w1zard350, если не жалко, выложи исходники, хотелось бы глянуть, что получилось...

Автор: math64 9.6.2009, 07:47
Для запуска компилятор не нужен. Но нужны драйвер (*.BGI)  и шрифты (*.CHR), причём если нужны русские буквы, то шрифты не из поставки компилятора, а переделанные. Это особенности библиотеки BGI.
 

Автор: w1zard350 13.6.2009, 18:00
Курсант, спасибо за предложенный Вами вариант, я его изучу. smile

Dmi3ev, а что именно интересует? Или выложить весь код, мне не жалко. Очень хочется, что его посмотрели знающие люди и высказали свое мнение smile

Автор: Dmi3ev 14.6.2009, 22:09
Цитата

Dmi3ev, а что именно интересует? Или выложить весь код, мне не жалко. Очень хочется, что его посмотрели знающие люди и высказали свое мнение

хотелось бы весь код, чтобы компильнуть его у себя, просто ехе требует дров...
интересно было бы посмотреть результат, причем давай сразу с ботом (ботами)... раз не жалко...

Автор: chaos 15.6.2009, 12:21
классический пример столкновения объектов описан у александреску в современном проектировании.

ЗЫ если я конечно понял смысл слова "столкновение" smile

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)