C++ Builder :: Игрописательство :: Как написать игру за 21 день ?

Главная

Введение

ЛИКБЕЗ
C++ Builder


ЛИКБЕЗ
C++

Глава №1
Глава №2
Глава №3
Глава №4
Глава №5
Глава №6

Исходники
к пособию

Мои исходники

Статьи
&
Книги


Гостевая

Ссылки

About

E-mail

Rambler's Top100


Создай игру на C++Builder за 21 день - Глава №4 (Черновой вариант)
 

Глава №4.

Многопоточные приложения.

Сегодня я решил сесть за клавиатуру и написать наконец четвертую главу. Надо же вас чем то взбодрить - а то на неделю выпал из жизни и вы меня потеряли ? Но ничего, я снова – онлайн. Как вы знаете, я меньше всего люблю заброшенные сайты. Этот сайт, надеюсь, таким не станет и я доведу дело до конца. Поэтому я здесь и поэтому я сейчас пишу эти строчки. Безусловно, получившийся вариант этой главы пока что - черновой. Не буду растягивать вступление и начну …

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

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

Сделать одновременное выполнение параллельно двух кусков кода невозможно физически (если имеется только один процессор), однако можно сделать псевдопараллельное выполнение – т.е выполняются по очереди команды то из одного куска, то из другого.

Таким образом, разделяют время ЦП (центрального процессорора) приложения в системе Windows (в ДОСе не было механизмов, для одновременного выполнения двух приложений, несмотря на это существовали резидентные программы которые постоянно сидели в памяти).

Windows с помощью встроенных средств очень быстро переключает приложения и нам кажется, что они выполняются одновременно(хотя на самом то деле это не так).

Как же быть, если требуется одновременное выполнение нескольких кусков кода в одном приложении? Нам надо воспользоваться потоками. Можно работать с потоками функциями WIN32, но это не очень удобно, тем более что в C++ Builder есть встроенный класс TThread.

Для чего все это может пригодиться в игре? Например, для одновременного движения всех героев. В tl-survival (я написал эту прогу еще до того, как узнал про треды) вы можете увидеть, что, когда движется главный герой, все остальные застывают на месте, а это, согласитесь, выглядит не очень то красиво. Чтобы исправить это, необходимо создать нитку для каждого героя (нужно запихать его в класс героя).

Рассмотрим это чуть позже, а сейчас вы узнаете, как в принципе сделать тред в вашей программе. В свое время я очень долго пытался сделать тред средствами WinAPI и лишь случайно обнаружил, что в Билдере это можно сделать гораздо проще (не сложнее чем создать новую форму).

Создать тред можно так: зайдите в File->New->Other и в появившемся окошке выберите Thread Object.

У вас попросят ввести его имя. Введите любое осмысленное название, к примеру, MyThread.

Теперь в вашем приложении появится новый Юнит с описанием класса Треда.

Его основная функция Execute. в этой самой Экзекуции должен быть прописан основной код процесса. Чтобы использовать поток в первом Юните не забудьте подключить его туда. И, в свою очередь, если вы надумаете использовать данные или объекты из первого Юнита не забудьте его включить во второй.


Смысл подключения заголовочных файлов состоит в том, что переменные и объекты из одного файла-Юнита вы сможете использовать в другом.
Стоит обратить внимание, что подключение 1-го Юнита ко второму не значит автоматического подключения 2-го к 1-му.

Подключать Юниты можно двумя способами.

1) В начале файла Unit1.cpp можно прописать #include "Unit2.h" (после строчки #include "Unit1.h").
2) Более простой и быстрый - Зайти в меню File -> Include Unit Hdr. Другое дело, что не рекомендуется пользоваться вторым способом, не зная и не понимая первого.


После подключения взаимного подключения двух юнитов в экзекуте вы можете написать такой код:

void __fastcall MyThread::Execute()
{
int i=0;
while (!Terminated)
{
i++;
Form1->Edit1->Text=i;
Sleep(50);
}
}
//---------------------------------------------------------------------------

Кстати, пример работы с Тредом можно скачать здесь >>

Перейдем к тредам в играх. Мы остановились с вами в прошлый раз на выделении героев мышкой. Теперь нам нужно объединить все знания, полученные нами из предыдущих глав и написать заново класс чувака. Вот приблизительно, что должно получиться:

class Chuvak
{
public : // обратить внимание !!! часто забывают прописать public
Chuvak(); // конструктор
~Chuvak(); // деструктор
bool selected; // выделен или нет
int type; // тип
int x1,y1; // координаты начала движения
int x2,y2; // координаты конца движения
int live; // жизни (здоровье или очки или что-то в этом роде)
int boolean_path[20][20];
MyThread *SecondThread; // поток ходьбы
void run_thread(void); // запуск потока
bool create_thread(void); // создание потока
void calc_path(int a[20][20],int h[20][20]); // расчет кратчайшего пути
void draw(TCanvas *Canvas); // отрисовка
};

Скачать готовый пример №12

Дам некоторые комментарии: чтобы персонажи не перемещались по кратчайшему пути мгновенно создается поток SecondThread. В котором движения (шаги) персонажи совершают каждые 50 мс. Почему такое название потока? Такое название по не помню уже какой причине было в примере и мне хочется его оставить так как было, чтобы вы не запутались … хотя, я чувствую,. вы и так запутаетесь ? (кажется первый поток FirstThread – был потоком атаки).

boolean_path – это двоичная карта, на которой двадцатками среди нулей изображен путь, по которому надо идти.

Когда герой идет, он стирает за собой двадцатки и оставляет нули. Следовательно, назад он уже не пойдет.

Вот демонстрация этого примера:

if(Form1->chuvaki[i].boolean_path[x1+1][y1]==20 && Form1->h[x1+1][y1]==0)
{
Form1->chuvaki[i].boolean_path[x1][y1]=0;
x1++;
}
if(Form1->chuvaki[i].boolean_path[x1-1][y1]==20 && Form1->h[x1-1][y1]==0)
{
Form1->chuvaki[i].boolean_path[x1][y1]=0;
x1--;
}
if(Form1->chuvaki[i].boolean_path[x1][y1+1]==20 && Form1->h[x1][y1+1]==0)
{
Form1->chuvaki[i].boolean_path[x1][y1]=0;
y1++;
}
if(Form1->chuvaki[i].boolean_path[x1][y1-1]==20 && Form1->h[x1][y1-1]==0)
{
Form1->chuvaki[i].boolean_path[x1][y1]=0;
y1--;
}

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

Chuvak::Chuvak() // конструктор чувака (вызывается, когда мы создаем его)
{
team=1; // по умолчанию - свой.
live=100;
SecondThread = new MyThread(true);
}

Chuvak::~Chuvak() // деструктор чувака (вызывается, когда мы удаляем его)
{
SecondThread->Terminate(); /// завершаем поток
delete SecondThread; /// удаляем поток
}

В общем, смотрите на пример и делайте как там ...

Спрашивайте что здесь непонятно, я отвечу вам на любые вопросы и, если потребуется, допишу ваши примечания в главу.

С уважением, ваш ToshibaNT !

 

 

 
 
Hosted by uCoz