Создание 3D игр на C++
Часть1. Урок 2
Создаем консоль
В этом уроке мы создадим консоль, в следующем мы ему добавим функциональности, разберемся с изменением разрешения экрана, и уже на послеследующем уроке приступим непосредственно к 3D программированию.
Итак, создание консоля. Откройте ваш проект и добавьте к нему новую форму (File->New; Form). Не надо портить форму, которая уже есть. Она нам еще будет нужна. Сразу сохраните форму под именем col_con.cpp, и назовите frmCon (свойство Name в инспекторе объектов). Установите форме свойство FormStyle в fsStayOnTop, чтоб она всегда показывалась поверх игры.
Начнем с задания некоторых констант и переменных:
#pragma resource "*.dfm"
TfrmCon *frmCon;
const EXT_MAX_STRINGS = 32;
const EXT_MAX_STRING_WIDTH = 60;
AnsiString EXT_SEPARATOR;
AnsiString Strings[EXT_MAX_STRINGS + 1];
AnsiString CurrentString;
int CurrStr;
Строка EXT_SEPARATOR будет хранить 60 знаков равно [=] (по количеству символов в строке, они задаются константой EXT_MAX_STRING_WIDTH). Сразу и зададим ее в конструкторе формы:
EXT_SEPARATOR = "";
for (int i = 0; i < EXT_MAX_STRING_WIDTH; i++)
EXT_SEPARATOR += "=";
И объявим как внешнюю в файле col_con.h
extern AnsiString EXT_SEPARATOR;
Strings - это массив самих строк
CurrStr - это номер текущей строки;
CurrentString - это строка, которую мы сейчас набираем. Их тоже сбросим в конструкторе:
CurrStr = 0;
CurrentString = "";
Теперь о том, как будем консоль выводить. Я здесь приведу не оптимальный вариант, вы можете изменить его на свое усмотрение.
Итак, выносим на форму компонент TImage и даем ему имя imgEdit. Разносим его на всю форму, но свойство Align НЕ В КОЕМ СЛУЧАЕ НЕ ТРОГАЕМ!!! Теперь форме поставим свойство AutoSize в true. Таким образом форма всегда будет подстраиваться под размер картинки.
Картинку надо подготовить. Опять же в конструкторе:
<01> imgEdit->Canvas->Font->Name = "Fixedsys";
<02> imgEdit->Canvas->Font->Size = 8;
<03> imgEdit->Canvas->Font->Color = clWhite;
<04> imgEdit->Canvas->Brush->Color = clBlack;
<05> imgEdit->Width = imgEdit->Canvas->TextWidth ("A") * EXT_MAX_STRING_WIDTH;
<06> imgEdit->Height = imgEdit->Canvas->TextHeight ("A") * EXT_MAX_STRINGS;
<07> imgEdit->Picture->Bitmap->Width = imgEdit->Width;
<08> imgEdit->Picture->Bitmap->Height = imgEdit->Height;
Здесь мы устанавливаем шрифт для консоля (01, 02, 03), фон (04), устанавливаем ширину, умножая ширину одного символа на их количество, и высоту, умножая на количество строк (05, 06). Для нахождения ширины и высоты я использую символ А, поскольку шрифт Fixedsys - моноширинный и все буквы имеют точно такой же размер. И, наконец, в последних двух строках мы устанавливаем размер растровой картинки в Image в размеры самого Image.
Итак, это, пока, вся инициализация, теперь напишем функции для добавления строк. Для начала напишем функцию, которая будет добавлять строку, предполагая, что она меньше (или равна) максимальной длине строки. Эта и следующая функции должны располагаться до конструктора формы, сразу после объявления переменных и констант.
void __fastcall AddString1 (AnsiString S)
{
if (CurrStr >= EXT_MAX_STRINGS)
{// Если строка не влезает уже
CurrStr = EXT_MAX_STRINGS;
for (int i = 1; i < EXT_MAX_STRINGS; i++) Strings[i]=Strings[i+1];
}
else CurrStr++;
Strings[CurrStr]=S;
}
Сначала мы проверяем, влезает ли еще одна строка. Если нет, то мы сдвигаем все предыдущие строки, а номер текущей не меняем, так как теперь она станет пустой (если новая стркоа не влезала, то, следовательно, текущей является самая нижняя строка. Мы их все сдвинули => текущая строка пустая). Если же место еще есть, то мы просто увеличиваем номер текущей строки. После этого в любом случае текущая строка будет пустой. И мы просто заменяем ее (пустую) на новую.
Теперь напишем функцию, которая добавит строку любой длинны, разбив ее на более мелкие и добавив по отдельности.
void __fastcall AddString (AnsiString S, bool _Refresh)
{
while (S.Length() > EXT_MAX_STRING_WIDTH)
{
AddString1 (S.SubString(1, EXT_MAX_STRING_WIDTH));
S.Delete(1, EXT_MAX_STRING_WIDTH);
}
AddString1 (S);
if (_Refresh && frmCon)
frmCon->Refresh ();
}
Функции Refresh пока нет (здесь имеется ввиду не стандартная функция Refresh), мы ее напишем следующей, а пока по этой пара слов. До тех пор, пока строка длинне, чем надо, мы добавляем отделяем от нее влезающую часть, и выводим с помощью функции AddString1. Потом мы добавляем оставшийся кусок, и в конце, если нас просят обновиться (_Refresh == true) и форма frmCon уже создана (например, во время инициализации или OnCreate формы еще не существует и вызов Refresh даст ошибку), мы обновляем консоль.
Теперь об обновлении. Строки мы добавили, пришло время отобразить их. Для этого мы у формы frmCon перепишем функцию Refresh:
bool _NowRefreshing = false; // Идет ли обновлние сейчас. Надо, чтобы не обновлялось дважды!!!
void __fastcall TfrmCon::Refresh ()
{
if (!_NowRefreshing)
{
_NowRefreshing = true;
imgEdit->Canvas->Brush->Color = clBlack;
imgEdit->Canvas->FillRect(Rect(0, 0, Width, Height));
// Добавим текущую строку
AddString ("NEW>" + CurrentString + "_", false);
// Выведем весь текст
for (int i = 1; i <= CurrStr; i++)
imgEdit->Canvas->TextOutA(0, (i-1)*imgEdit->Canvas->TextHeight ("A"), Strings[i]);
// Удалим текущую строку
while (Strings[CurrStr].SubString(1, 4) != "NEW>") CurrStr --;
CurrStr --;
if (frmCon)
TControl::Refresh ();
_NowRefreshing = false;
}
}
Заметьте, что так и впрямь можно делать. Не смотря на то, что форма уже наследует метод Refresh от родителя TControl, мы можем написать одноименный метод! После этого для вызова старого метода надо написать TControl::Refresh ();
Что делает функция. Во-первых, она проверяет, не запущена ли она уже. Может произойти так, что одна из вызываемых подфункций могут снова вызвать Refresh (ведь обноление мы вызываем довольно часто). Для этого мы в начале запоминаем, что уже обновляемся, и до самого конца функция больше не разу не запустится.
После этого мы закрашиваем картинку в черный цвет. Затем добавляем текущую вводимую строку. Добавляем с приставкой NEW> После строчка за строчкой выводим весь текст на картинку. Затем мы ищем строку, начинающуюся на NEW> и устанавливаем последней ей предшествующую. Это, на мой взягляд, самый простой способ вывести вместе со всем текстом текущую строку тоже.
Еще раз поясню. Мы храним в переменых Strings весь текст, кроме вводимой в настоящий момент строки. При обновлении же нам надо вывести в конце текста еще и текущую строку. Потому мы попросту добавляем ее в общий текст, выводим его, а потом удаляем оттуда, просто установим номер последней строки (CurrStr) перед текущей вводимой.
В конце мы вызываем стандартную функцию обновления, которая перерисует всю форму, и снимаем флаг того, что обновление запущено.
Заметьте, что функция принадлежит классу формы (TfrmCon::), в отличие от двух предыдущих, которые были объявлены глобально. А раз функция принадлежит классу формы, то надо ее в этом классе объявить. Перейдите к заголовочнуму файлу col_con.h и в класс формы добавте:
__published:
TImage *imgEdit;
// Следующую строку надо добавить!
void __fastcall Refresh ();
Ну и, наконец, последнее: обработка нажатия клавиш:
Выберите форму в списке объектов инспектора объектов, и на вкладке Events дважды щелкните по событию OnKeyPress. Редактор кода автоматически создаст метод. Напишите в него:
void __fastcall TfrmCon::FormKeyPress(TObject *Sender, char &Key)
{ // Добавление буквы
if (Key == '~' || Key == '`' || Key == 'ё' || Key == 'Ё')
// Тильда - закрыть консоль
Hide ();
else if (unsigned(Key) >= unsigned(' ') && unsigned(Key) <= unsigned('я'))
// Иначе добавить символ
CurrentString = CurrentString + Key;
Refresh();
}
Тут, думаю, все понятно. Теперь обработаем другие клавиши. Добавьте метод OnKeyDown:
void __fastcall TfrmCon::FormKeyDown(TObject *Sender, WORD &Key,
TShiftState Shift)
{ // Опрос функциональных клавиш
if (Key == VK_RETURN)
{ // Enter - перевод строки
AddString (CurrentString, false);
CurrentString = "";
}
else if (Key == VK_BACK && CurrentString.Length() > 0)
// Backspace = забить последный символ
CurrentString.Delete(CurrentString.Length(), 1) ;
else if (Key == VK_ESCAPE)
// Esc - закрыть консоль
Hide ();
Refresh();
}
Здесь, если нажат Enter, то мы добавляем текущую вводимую строку и обнуляем ее, если Backspace, то забиваем последний символ, если Escape, то закрываем консоль. Тут все тоже просто.
Итак, первая часть по созданию консоля закончена. На следующем уроке мы научим его обрабатывать команды, инициализируем DirectX (в лице NukeDX) и установим разрешение экарана.
В заключение добавлю, что сейчас вам ничего не стоит добавить в конструктор формы, в самый его конец, функции, которые при инициализации консоля сразу выведут информацию о вас. В моем случае это выглядит так:
AddString (EXT_SEPARATOR, false);
AddString ("Coliseum 3D", false);
AddString ("© SHD-ALakazam [shd@bk.ru]", false);
AddString (EXT_SEPARATOR, false);