Среда, 15 Августа 2018, 04:56

Приветствую Вас Гость

Меню сайта
Категории каталога
Создание игр [309]
Статьи об общих понятиях связанных с созданием игр.
Программирование [69]
Гайды по программированию на разных ЯП.
Движки и Гейммейкеры [123]
Статьи о программах для создания игр, уроки и описания.
Софт [27]
Различные программы, в том числе в помощь игроделам.
2D-графика [11]
Уроки по рисованию, растр, пиксель-арт, создание спрайтов и пр.
3D-графика [10]
Уроки по моделированию, ландшафт, модели, текстурирование и пр.
Моддинг игр [4]
Модификация компьютерных игр, создание дополнений, перевод, хакинг.
Игры [81]
Статьи об играх, в том числе и сделанных на гейммейкерах.
Разное [63]
Статьи, которые не вошли в определённые разделы.
Наш опрос
Игры какого типа вы предпочитаете делать?
Всего ответов: 16277
Главная » Статьи » Создание игр

Своя 2D инди игра на Delphi (7) без DirectX - это просто!
Статья будет пополняться по частям и не сразу (извините, но создание полноценной статьи очень тяжкий интеллектуальный труд.)

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

Часть I.
Введение.
Некоторые программисты Delphi (у меня тоже такое было) иногда жалеют, что они решили осваивать именно эту среду разработки, а не более актуальные такие как C++ или игровые движки вроде Unity. От этого у них возникает вопрос "Так стоит ли вообще начинать разрабатывать игру на Object Pascal? Может начать изучать что-то другое?". Отвечу сразу - определенно стоит. Начать осваивать что-то новое вы всегда успеете, как говорится - нет предела совершенству. А ваши накопившиеся знания Delphi не должны пропасть даром. Да, вполне возможно C++ более гибок и документации по созданию игр на нем намного больше, но я вас уверяю, с помощью Delphi тоже можно получить отличный результат. Один из ярких примеров - это игра Soldat 2D. Ведь главное в разработке игры - это все таки результат, а не способ его достижения (но он тоже очень важен).

С чего же начать?
Самое главное при старте разработки вашего проекта (если основная идея уже давно пылится в вашей голове) является не выбор версии Delphi или создание папки вашего проекта на жестком диске, а ваша команда. Скажу сразу - программировать в одиночку - очень тяжкое дело. В интернете полно способов как все-таки не забросить проект - разрабатывать сразу несколько проектов, или насильно заставлять себя доделывать до конца, но самый действенный - это, как минимум, один ваш напарник, даже если он ВООБЩЕ не разбирается в программировании, но, желательно, чтобы он тоже был очень заинтересован в вашем ОБЩЕМ проекте. Ведь если у вас есть такой напарник, то, как минимум, сама мысль оставить проект будет уже менее приятной, т.к. есть тот, кто на вас надеется и вы не можете его вот так просто подвести. Но самое главное, что этот человек будет помогать вам в остальном - дизайне, создании карт на вашем редакторе и просто источником сил для продолжения проекта. Одна голова хорошо - а две ещё лучше!

А теперь начнем.
Вот вы уже выбрали версию Delphi (оставлю выбор за вами, т.к. считаю это не таким важным моментом), создали папку для проекта, и я надеюсь, что вас как минимум двое. Теперь мы разберемся, что самое главное в игре? На этот вопрос нет общего ответа, т.к. каждому в каждой игре нравится что-то своё. Кому-то нравится сюжет, кому-то графика, кому-то музыка. Но при создании игры (а точнее игрового движка) все-таки одним из главных моментов - это разработка графической составляющей. Ведь игра - это в первую очередь компьютерная программа, которая отображает всю информацию на мониторе, а как известно, человек воспринимает более 90% информации с помощью своего зрения. Поэтому в данной статье я постараюсь наиболее полно рассказать о внедрении графики в свой проект, а остальное (звук например) я оставлю на вас.
Так что же может послужить графическим ядром (кроме DirectX) нашего проекта? На этот вопрос тоже очень много ответов в интернете, но я заострю внимание в данной статье на библиотеке (а точнее наборе модулей) FastLIB.

Рассмотрим плюсы и минусы данной библиотеки:
+ Простота использования (по сравнению с DirectX в десятки раз);
+ Отличная скорость прорисовки (fps может доходить до 500);
+ Набор всех необходимых функций (альфа-каналы, масштаб, повороты ...);
+ Возможность создания любых своих спец-эффектов;
+ Открытый код для возможности модификации библиотеки;
Но есть один БОЛЬШОЙ минус:
- Вся нагрузка приходится на процессор и ни единого намека на использование видео-карты;

Но не стоит бояться этого минуса, ведь даже на слабеньких старых процессорах (у меня старенький одно-ядерный Pentium 4 - 3000 Гц) при нескольких тяжелых спец-эффектах частота держит норму в 60 fps. А большая (намного большая) часть пользователей уже давно имеет несколько-ядерные машины.

Внедрение FastLIB в проект.
Так как же использовать библиотеку FastLIB? На самом деле самое сложное, с чем я в ней сталкивался, это включение альфа-канала. Для начала скопируйте все модули библиотеки в папку с проектом и включите их в проект через Delphi любым привычным способом (эту задачу я снова оставлю на вас). После этого можете открыть и изучить самый главный модуль - FastDIB. В нем описан самый главный класс - TFastDIB. По сути дела это тот-же TBitmap, но направленный на получение большей скорости при отрисовки. И все функции библиотеки используются именно к этому классу TFastDIB. Далее, что бы мы не использовали - будь то текстура, или спрайт, или поверхность, она обязательно будет основываться на классе TFastDIB.

Часть II.
Основные функции класса TFastDIB.

Так как класс TFastDIB является объектом, то его необходимо создавать при начале и уничтожать в конце работы. Осуществляется это при помощи функций Create и Destroy. Создавать объект необходимо сразу как возникает необходимость работы с ним, но только один раз, иначе создастся новый объект, а прошлый потеряется в памяти компьютера. Уничтожать так же следует только после того, как объект уже не используется и никогда уже не будет использован.

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

Пример создания, отрисовки и уничтожения переменной типа TFastDIB:
Код
var sprite: TFastDIB;
  ...
  // создание объекта
  sprite := TFastDIB.Create;
  ...
  // манипуляции с объектом
  ...
  // отрисовка на форму
  sprite.Draw(Form1.Canvas.Handle,0,0);
  // уничтожение объекта
  sprite.Destroy;
  ...


В данном примере мы отрисовываем sprite на дескриптор поверхности окна в координатах 0x0, но в коде нет примера, который бы заполнял наш спрайт данными. Т.е. мы на самом деле ничего не получим на нашем окне. Для того, чтобы что-то нарисовать, нужно чтобы было что рисовать. На этот случай есть два варианта - либо мы сначала что-то рисуем на самом sprite, либо мы загружаем картинку из файла с помощью функции LoadFromFile.

Пример создания, загрузки, отрисовки и уничтожения переменной типа TFastDIB:
Код
var sprite: TFastDIB;
  ...
  // создание объекта
  sprite := TFastDIB.Create;
  // загрузка из файла
  sprite.LoadFromFile('picture.bmp');
  ...
  // манипуляции с объектом
  ...
  // отрисовка на форму
  sprite.Draw(Form1.Canvas.Handle,0,0);
  // уничтожение объекта
  sprite.Destroy;
  ...


Примечание: с помощью этой функции можно загружать графические данные только формата *.bmp.

Так же предусмотрена возможность сохранения результата с помощью функции SaveToFile.

Рассмотрим все возможные способы отрисовки и их функции:
Draw(fDC:HDC;x,y:Integer) - копирует содержимое TFastDIB на дескриптор fDC в координатах [x;y];
Stretch(fDC:HDC;x,y,w,h:Integer) - растягивает содержимое TFastDIB на дескриптор fDC в координатах [x,y] в ширину w и высоту h;
TransDraw(fDC:HDC;x,y:Integer;c:TFColor) - копирует содержимое TFastDIB на дескриптор fDC в координатах [x;y] кроме пикселей с цветом c;
TransStretch(fDC:HDC;x,y,w,h:Integer;c:TFColor) - растягивает содержимое TFastDIB на дескриптор fDC в координатах [x,y] в ширину w и высоту h кроме пикселей с цветом c;
AlphaDraw(hDC,x,y,a,hasAlpha) - копирует содержимое TFastDIB на дескриптор fDC в координатах [x;y] с непрозрачностью a с поддержкой альфа-канала (hasAlpha = true) и без поддержки альфа-канала (hasAlpha = false);
AlphaStretch(fDC:HDC;x,y,w,h:Integer;a:Byte;hasAlpha:Boolean) - растягивает содержимое TFastDIB на дескриптор fDC в координатах [x,y] в ширину w и высоту h с непрозрачностью a с поддержкой альфа-канала (hasAlpha = true) и без поддержки альфа-канала (hasAlpha = false);
TileDraw(fDC:HDC;x,y,w,h:Integer) - заполняет область поверхности с дескриптором fDC в координатах [x;y] размером w на h;

Использование альфа-канала.
Одним из важных моментов каждой игры (если рассматривать только графическую составляющую) является наличие красивых спец-эффектов, будь то эпичные взрывы или стеклянный интерфейс. Для этого используют полупрозрачные спрайты, а именно спрайты 32-х битного режима. Это значит, что на один пиксель спрайта выделяется 32 бита памяти, а именно 4 байта - r,g,b и альфа-канал. Каждый из байт может содержать значение от 0 до 255. Таким образом альфа-канал - это степень непрозрачности каждого пикселя от 0 до 255. Ниже будет приведен пример создания спрайта с использованием альфа-канала и случайным заполнением каждого пикселя:
Код
var  
  c: PFColorA;
  x,y: integer;
  sprite: TFastDIB;
begin
  ...
  // создание спрайта
  sprite := TFastDIB.Create;
  // изменение размера и режима спрайта
  sprite.SetSize(64,64,32);
  // очистка черным цветом
  sprite.Clear(tfBlack);
  // находим все биты спрайта
  c := Pointer(sprite.Bits);
  // сканирование каждого пикселя
  for y := 0 to sprite.AbsHeight-1 do
  begin
  for x := 0 to sprite.Width-1 do
  begin
  // задаем случайную прозрачность пикселя
  c.a := random(256);
  // задаем случайный цвет
  c.b := (random(256) * c.a) div 255;
  c.g := (random(256) * c.a) div 255;
  c.r := (random(256) * c.a) div 255;
  // переключаемся на следующий пиксель
  Inc(c);
  end;
  // переходим на следующую линию
  c := Ptr(Integer(c)+sprite.Gap);
  end;
  // отрисовка на форму
  sprite.AlphaDraw(Form1.Canvas.Handle,0,0,255,true);
  // уничтожение объекта
  sprite.Destroy;
  ...


Данный код генерирует картинку размером 64x64 пикселя с режимом 32 бита (с поддержкой альфа-канала) со случайными цветами и случайной маской прозрачности, отрисовывает результат на форме.

Примечание: если вы хотите использовать спрайт с альфа-каналом, то отрисовывать его вы сможете только функциями AlphaDraw и AlphaStretch, т.к. только они поддерживают возможность отрисовки спрайта с альфа-каналом с прозрачностью.

Давайте рассмотрим приведенный выше код поподробнее:
переменная c типа PFColorA - это указатель на переменную типа TFColorA, который является записью и состоит из 4 байт:
Код
PFColorA =^TFColorA;
  TFColorA = packed record
  case Integer of
  0: (i: DWord);
  1: (c: TFColor);
  2: (hi,lo: Word);
  3: (b,g,r,a: Byte);
  end;

Т.е. это указатель на данный пиксель нашего спрайта.
c := Pointer(sprite.Bits); - здесь мы указываем ссылку на все биты нашего спрайта.
Inc( c ); - здесь мы смещаемся на один пиксель дальше.
c := Ptr(Integer( c )+sprite.Gap); - здесь происходит перемещение на новую линию спрайта.
На самом деле вся картинка нашего спрайта это одна сплошная линия из пикселей, а именно одномерный массив. Т.е. в нашем случае это 64x64=4096 пикселей, а точнее 4096*4=16384 байта (Gap = 0). А переключение на следующий пиксель происходит за счет смещения адреса указателя c (Int( c )) в нашем одномерном массиве Bits. А так как мы изначально задали 32-х битный режим, то программа при смещении указателя смещает его на 4 байта автоматически.

Система игрового движка.
Теперь мы рассмотрим как же все-таки делаются все игры, и как нам начать делать свой собственный рабочий движок.

Раньше, когда я был совсем маленький, я играл со своим братом в настольные бумажные игры с кубиком. Это было очень увлекательно и забавно, но когда мы играли, то мы сами за всем следили и регулировали все правила игры, сами двигали все фигурки, сами считали баллы. Но потом у старшего брата появилась дэнди. Приставка сама все делала за нас и во много раз быстрее. Играть в игры стало интереснее и увлекательнее, т.к. мы не думали ни о чем лишнем, а были максимально сконцентрированы на самом игровом процессе, а если быть проще - тупо пялились в ящик и ломали джостики. Потом у брата появилась сега мега драйв 2, потом сега дримкаст, а потом у каждого дома уже был ПК. Игры становились все красивее и увлекательнее. Но меня всегда мучил один и тот же вопрос - "КАК?". Как работают все игры? Сама игра казалась чем-то волшебным. Мой склад ума не позволял понять всю сущность происходящих процессов, пока я сам не стал заниматься программированием. А когда я все-таки узнал "КАК", то реальность оказалась намного проще нелепых догадок.

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

Для "зацикливания" нашего игрового движка так же существует много способов, но я вам расскажу о самом простом и доступном - использование стандартного компонента TApplicationEvents (вкладка Additional на панеле компонентов). Добавьте этот компонент на форму и создайте обработчик события OnIdle. Именно в этом месте и будет происходить считывание кода до конца работы программы.
Код

procedure TForm1.ApplicationEvents1Idle(Sender: TObject;
  var Done: Boolean);
begin
  // проверка устройств ввода, объектов, прорисовка сцены и вывод на экран
  ...
  done := false;
end;


Примечание: done - переменная, отвечающая за прекращение выполнения процедуры в следующем цикле при неактивности пользователя (например если пользователь не трогает устройства ввода - мышь и клавиатуру).

Именно здесь будет проверяться вся система нашего игрового движка. Но чтобы что-то проверять - нужно все это создать.

Графическое устройство.
Вот и настал момент поближе познакомиться с графической составляющей нашего проекта. Сейчас мы рассмотрим один из примеров системы графики.

Всю работу графической системы можно разделить на три части: создание графических устройств, их проверка и отрисовка, уничтожение графических устройств. Т.е. все это можно описать в 3-х функциях:
CreateGraphicsDevices;
CheckGraphicsDevices;
DestroyGraphicsDevices;

Рассмотрим каждую функцию поподробнее:

CreateGraphicsDevices;
Здесь происходит установка нужного режима экрана, создание главной поверхности, загрузка текстур, создание других необходимых графических устройств. Чем же является "главная поверхность"? По сути дела это тот же TFastDIB, но главная она из-за того, что все объекты, весь интерфейс, все спец-эффекты рисуются именно на ней, а она уже в конце проверки графики рисуется на поверхности формы и отображает весь результат на экране.

Продолжение статьи (13.11.2013).

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

Другой способ это загрузка по необходимости. Т.е. если появилась необходимость использования нужной текстуры, то только после этого она загружается. Этот способ экономит память, но добавляет нагрузки процессору, т.к. при необходимости использования нужно проверять загружена ли текстура, а если нет - то загружать. При этом способе также нужен правильный выбор - либо она сразу после использования и дальнейшей ненадобности очищается, либо остается в памяти до следующего использования.

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

Примеры объявления и использования динамических массивов:
Код
...
var
  // объявляем одномерный целочисленный массив
  m1: array of integer;
  // объявляем двухмерный вещественный массив
  m2: array of array of real;
  ...
begin
  ...
  // изменяем размер одномерного массива (10 элементов)
  SetLength(m1,10);

  // изменяем размер двухмерного массива (10x10=100 элементов)
  SetLength(m2,10,10);

  // задаем нулевой (самый первый) и 9-ый (самый последний) элемент одномерного массива
  m1[0] := 1992;
  m1[9] := 20;

  // добавляем к самому первому самый последний
  m1[Low(m1)] := m1[Low(m1)] + m1[High(m1)];

  // задаем элемент нулевого столбца нулевой строки двухмерного массива
  m2[0][0] := 2013;
  ...
end;


ОЧЕНЬ ВАЖНОЕ ПРИМЕЧАНИЕ!!!
Существует очень большая вероятность того, что вы будете использовать указатели на элементы динамических массивов, и при этом в ходе работы программы будут происходить фатальные ошибки. Ошибки могут происходить из-за того, что после установки указателя на какой-либо элемент массива вы его изменяете (добавляете или удаляете элементы). После изменения числа элементов массива программа может перенести весь массив на новый адрес в оперативной памяти компьютера (т.к. массив должен быть цельным и ему может не хватить места с новым размером на старом адресе), а ваши старые ссылки будут ссылаться на ложный (старый) адрес.
Будьте внимательны! При изменении размера массивов заново назначайте указатели на их элементы!

Теперь разберем основные функции для работы с динамическими массивами.
SetLength(array,size) - устанавливает новый размер size массива array;
Low(array) - возвращает индекс самого первого (нижнего) элемента массива array;
High(array) - возвращает индекс самого последнего (верхнего) элемента массива array;

(здесь последует продолжение статьи, но чуть позже)
Категория: Создание игр | Добавил: beskor (28 Сентября 2013)
Просмотров: 10970 | Комментарии: 14 | Рейтинг: 3.4/12 |
Теги: FastLIB, Object Pascal, TFastDIB, Soldat 2D, FastDIB, pascal, без DirectX, Программирование, delphi, C++
Дополнительные опции:
Также если вы считаете, что данный материал мог быть интересен и полезен кому-то из ваших друзей, то вы бы могли посоветовать его, отправив сообщение на e-mail друга:

Игровые объявления и предложения:
Если вас заинтересовал материал «Своя 2D инди игра на Delphi (7) без DirectX - это просто!», и вы бы хотели прочесть что-то на эту же тему, то вы можете воспользоваться списком схожих материалов ниже. Данный список сформирован автоматически по тематическим меткам раздела. Предлагаются такие схожие материалы: Если вы ведёте свой блог, микроблог, либо участвуете в какой-то популярной социальной сети, то вы можете быстро поделиться данной заметкой со своими друзьями и посетителями.

Всего комментариев: 14
+2-
11 Akopov   (11 Октября 2013 17:50)
Akopovвот теперь, когда ты добавил код, всё стало гораздо лучше, +

+0-
7 Ordan   (08 Октября 2013 02:31)
OrdanСам пишу на дельфе и пока не встречался с тем, что не может сделать дельфя, а другие языки могут.
Автор, почему вы используете "ApplicationEvents1Idle", а не таймер? Таймером можно получить больше фпс. И пока я не заметил чем же эта библиотека лучше ZenGl, хотя я пишу на голом опенГЛ и меня все устраивает)

+1-
8 beskor   (08 Октября 2013 14:32)
На счет таймера я не знаю, особо не интересовался, но думал, что ApplicationIdle работает точно так же как и таймер с нулевым интервалом, т.е. каждый цикл программы.
Может быть библиотека и хуже ZenGL, я не спорю, но она не основана на OpenGL и использует чисто стандартные функции отрисовки windows. И заточена она чисто на 2d грфику, а не для игр.

+1-
10 Ordan   (09 Октября 2013 02:02)
OrdanЗачем использовать нулевой интервал таймера, я в таймере использую минимум 10, нулевым интервалом ты съедаешь море цп. Для простых приложений еще норм, но ты же делаешь игру где будет море картинок и анимаций.
Продолжи статью и на этом примере посмотрим хороша ли библиотека.

+1-
12 beskor   (12 Октября 2013 23:53)
Конечно, можно ставить таймер с интервалом в 10 циклов (или миллисекунд, не знаю в чем там) для экономии цп, все равно монитор отобразит только один результат из ~30 отрисованных, это уже вопрос внедрения вертикальной синхронизации и расчета частоты обновления экрана, но моя система основана на расчетах и проверки всех устройств в режиме реального времени (в каждом цикле). я например вообще все стараюсь измерять не в циклах, а в миллисекундах для большей точности.
А если хочешь посмотреть хороша ли библиотека, то можешь подождать первой версии моей игры, я как раз пишу статью на основе своей игры.

+1-
5 al_mt   (05 Октября 2013 18:52)
Блин. А вот чего я категорически не понял - это можно ли в FastDIB загрузить картинку с готовым альфа-каналом или хотя бы в виде отдельной матрицы.

+1-
6 beskor   (07 Октября 2013 16:11)
как известно, картинка в формате bmp может иметь разрешение 32 бита (с поддержкой альфа-канала), а вот на счет загрузки через FastDIB сам не знаю, может быть такая возможность присутствует - нужно проверить...

+1-
9 beskor   (08 Октября 2013 15:19)
Уже есть версия 4.0, там есть поддержка загрузки картинок формата png. Ура!

+0-
13 al_mt   (20 Октября 2013 14:17)
Где?
Скачал, то что назвалось 4-й версией, но оно png не понимает cry

+0-
14 beskor   (26 Октября 2013 14:59)
Там есть доп. модуль FastGPlus.pas и в нем есть функция
gpLoadImage, она может загружать .png формат. Например:
...
var t: TFastDIB;
...
gpLoadImage('image.png',t);
...

+1-
2 al_mt   (01 Октября 2013 17:37)
2D на Delphi хорошо и чисто "родными" средствами получается.
Я долго медитировал ибо дельфинист, но потом всё же выбрал js
Причина простая - Delphi это винда. С некоторыми усилиями код портируется под Lasarus (не без траблов). Про большинство мобильных платформ можно забыть.
Увы...

+1-
3 noname   (02 Октября 2013 18:20)
nonameэто какими же родными средствами на Delphi хорошо получается 2D? насколько хорошо?

короч- 2D-движки бывают просто необходимы. для Delphi есть отличный ZenGl.

+1-
4 al_mt   (02 Октября 2013 19:08)
В Delphi 7 я начинал писать 2D платформер. А ранее неоднократно писал всякие мелкие игры-стрелялки.
Чего нет:
Альфа-канала нормального.
Встроенных эффектов.
Но если поставить RX - сразу появляется smile

Всё остальное - есть тупо банальными TImage + TMediaPlayer.
Основные недостатки - более высокая трудоёмкость и привязка к одной платформе.
Собственно вот только по этим причинам я сейчас пишу на js.

То что движки есть, так я и не возражаю. Это снижает трудоёмкость, но создаёт необходимость учить движок - а лень biggrin
И опять же повторюсь, привязка к платформе sad Lazarus не спасает, увы...

P.S. За то на дельфах довольно удобно писать всяких роботов для имитации человеков. Я так и всяких систем парсил на данные, которые скриптам не дают. cool Даже гугл и яндекс при правильной настройке впадают в недоумение biggrin biggrin biggrin

+0-
1 Akopov   (29 Сентября 2013 17:24)
Akopovсамого урока не узрел. ни строчки кода, сплошная реклама библиотеки

Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Поиск по сайту
10 случ. движков
  • CoolBasic
  • Nuclear Basic
  • Leadwerks
  • Reality Factory
  • Mirage RPG Creator
  • Golden Realm
  • Квестер
  • Realm Crafter
  • Vavoom
  • RPG Toolkit DS
  • Друзья сайта
    Игровой форум GFAQ.ru Перевод консольных игр
    Все права сохранены. GcUp.ru © 2008-2018 Рейтинг