Школьные поделки: Battlecruiser & DeathCraft

:

ОСТОРОЖНО! Концентрированная ностальгия! Описанное здесь морально устарело много-много лет назад. Более того, оно устарело до того, как было реализовано. Из статьи вы не узнаете ничего нового.
В давние-давние времена, когда Embarcadero CodeGear Borland Delphi RAD Studio C++ Builder был версии 4, когда Autodesk 3ds Max Discreet 3dsmax 3D Studio Max был версии 3, когда 80 Гб и 200 МГц были не в холодильниках, а в компьютерах, когда Интернет был неведомым и таинственным существом, каждое прикосновение к которому было магическим… я учился в старших классах школы и учился программировать. А также моделировать. И ещё всякое-разное по мелочи.
Подозрительный скриншот:


Папа был ментом-программистом (сейчас просто мент коп). И купил он комп — теоретически для подработок, но вертолётный завод как-то слишком резко усох, и комп использовался (по назначению) редко. Несколько раз наблюдал картину: всякие кнопочки, формочки, всякое-разное нажимается, а потом оно работает (был это C++ Builder). Глаза загорелись: это же круто — рисуешь кнопочки, а потом оно само что-то делает! Меня пустили формочки порисовать.
Однако возникла проблема: кнопочки рисуются, но вот что-то работать не хотят! Внезапно выяснилось, что в чудо-программе одним рисованием кнопки не задвигаются, нужно писать какой-то странный непонятный код. Мне сунули книжку по Билдеру. Внезавно выяснилось, что перед её прочтением весьма полезно хоть немного знать Си… Мне сунули книжку по Си.
Получилось странное: книжка по Си с программами, которые в Билдере нифига не работают (понятно, что запустить при умении можно, однако такого рода шаманствам я был необучен), и книжка по Билдеру, которая требует познаний в Плюсах (вот здесь класс, вот здесь метод, вот здесь заголовочный файл...). Но что-то начинало шевелиться. Перепечатывание кода из книжки — занятие не для слабонервных (это потом я уже узнал, что к книжке ещё диск полагался...), но жажда познания тащила. И самое интересное: в самом конце книжки по Билдеру была программа, работающая с графикой. Выводились спрайты! Цветные картинки, фигурно обрезанные, с анимацией! (Все, наверное, уже поняли, что за книжка у меня была — в те времена её кривой скан прилагался к каждому пиратскому диску с Билдером.)
Суть метода: берём картинку, будущую прозрачную область заполняем чёрным цветом, рядом располагаем маску: объект — чёрным, фон — белым. Выводим на канву сначала маску в режиме SrcAnd, потом картинку в режиме SrcPaint. Чтобы это всё не умерцалось, сначала рисуем всё в картинку в памяти, потом выводим на канву на форме. Был раскрыт секрет магии! И понеслось…

Краткая сводка:
Жанр: side-scroller
Графика: спёрта из StarCraft (юниты), спёрта из Motoracer (экран победы)
Звуки: спёрты шут знает откуда (у меня валялась куча звуков, выковырянных из игр)
Музыка: спёрта шут знает откуда (кто-нибудь знает, откуда мог взяться hitgm.mid?)
Используемый софт: C++ Builder, Sound Recorder, MS Paint

Скрин из игры:

Попробовал C++ Builder 6 — эта программа завелась! Правда в режиме совместимости со хрюшкой (старый TMediaPlayer накрывается медным тазом без режима совместимости). Несмотря на наличие подробной инструкции к каждому уровню, пройти толком не удалось… сложно.
Скачать игру Battlecruiser (770 KB, сорцы и исполняемый файл прилагаются, запускать в режиме совместимости)
Однако моим любимым жанром был RTS, поэтому довольно скоро я сел писать свою версию угадайте чего…

Краткая сводка:
Жанр: real-time strategy
Графика: спёрта из StarCraft, нарисована самостоятельно
Звуки: спёрты шут знает откуда
Речь: записана своя
Музыка: спёрта шут знает откуда
Используемый софт: C++ Builder, 3D Studio Max, MS Paint, Sound Forge
В процессе написания игры я учился 3D-моделированию.
Знакомый скриншот?

Вы когда-нибудь пробовали пользоваться 3D Studio Max на Pentium 200 MHz, 32 MB RAM? Скажу я вам, процесс хорошо тренирует нервы. Студия только грузится минут 10-15… рендеринг самых простых моделек по минуте… рендеринг одного кадра космического полёта из прилагаемого примера минут по десять… некоторые вещи типа кисточки весов модификатора кожи просто подвешивали компьютер… Но! Можно рисовать крутые вещи!
Изначально к игрушке прилагался ролик-заставка с двумя роботами, идущими с дулами друг на друга. Перед тем, как показать почтенной публике, решил перерендерить видео, но его не нашёл. Нашёл сцену с другим роботом, примерно той же эпохи. Теперь на заставке этот робот.
Последний кадр анимации, пора жать Start.

Вы когда-нибудь пробовали пользоваться C++ Builder на Pentium 200 MHz, 32 MB RAM? Это, конечно, не 3D Studio Max, но компиляция элементарного проекта типа моего (хотя он тогда для меня казался совсем не элементарным) могла занимать под минуту.
C++ Builder 6 со старым проектом:

Вы когда-нибудь… а хотя не, Sound Forge на древних компах работал вполне себе шустро. Теперь к эффектам эха, ускорения, замедления, разворота из стандартной Sound Recorder добавились эффекты с непонятными названиями типа Flanger и Wah-wah. Звуки можно было нормально увязывать друг за дружкой. Звучало круто. Даже динозавр говорит моим голосом.
Давайте запустим:

Некоторые юниты выдернуты из StarCraft. Тогда я не знал, что графику можно выдёргивать кошерными способами, и пользовался старым-добрым Print Screen. И редактировал в MS Paint. Старался принтскринить в зимних локациях, чтобы пиксели было легче отковыривать. Вы спросите: MS Paint и 3D Studio Max — не странный ли наборчик? Странный. Однако откуда мне было знать про другие редакторы — про эти ваши интернеты я тогда и не слыхивал. Я ж ни одно решение загуглить не мог — или сам, или никак. Папа со своими базами данных в моих игрушках тоже ни в зуб ногой…
Некоторые юниты нарисованы в 3D Studio Max. Как и весь софт, брался он на «чеховском рынке» (казанская версия «горбушки»). Книжку мне родители купили. Туториал к программе страшный, ужасный, на английском и без исходников прилагался (или это уже в следующей версии было?..). Что-то наковырялось. В основном роботы.
В игре есть AI. Противник строит здания (по списку, располагает по кругу относительно командного центра), строит собирающие юниты (до лимита), строит атакующие юниты (до лимита), атакует игрока (при наборе минимума атакующих юнитов). AI никакой, в общем-то, но своё дело знает, и игрока выносит весьма эффективно.
Противник строит бараки всей охапкой SCV и тренирует подкрепление:

Алгоритм поиска пути отсутствует, юниты упираются в первое же препятствие, так что их лучше отправлять по диагонали — если все препятствия прямоугольные, то их можно будет обойти. Юниты имеют неприятную особенность слипаться в одну точку. Для юнитов игрока задаётся разброс, а вот комп иногда ходит одной неубиваемой стопкой (привет Civilization).
Роботы на защите родной базы:

Иногда эффекты рисовались на совесть, иногда было лень, и лепились графические примитивы.
Яичница:

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

  for(i=0;i<MAX_BUILD;i++)
    if(Building[i].alive){
      if(Building[i].hits<=0)Building[i].dying=true;
      if(Building[i].dying){
        int boomcount=1;
        switch(Building[i].type){
          case 0: case 1: case 2: boomcount=5; break;
          case 3: boomcount=1; break;
        }
        sndPlaySound("Sound\\Boom\\Boom1",SND_ASYNC+SND_NODEFAULT);
        for(int k=0;k<boomcount;k++){
          for(j=0;j<MAX_BOOM;j++)
            if(!Boom[j].alive)
              break;
          if(boomcount==1){
            Boom[j].x=Building[i].x+Building[i].sqleft*16+Building[i].sqwidth*8-22;
            Boom[j].y=Building[i].y+Building[i].sqtop*16+Building[i].sqheight*8-25;
          }
          else{
            Boom[j].x=Building[i].x+Building[i].sqleft*16+random(Building[i].sqwidth*16);
            Boom[j].y=Building[i].y+Building[i].sqtop*16+random(Building[i].sqheight*16);
          }
          Boom[j].alive=true;
          for(int x=Building[i].sqleft;x<Building[i].sqwidth+Building[i].sqleft;x++)
            for(int y=Building[i].sqtop;y<Building[i].sqheight+Building[i].sqtop;y++)
              StopGrid->Cells[Building[i].x/16+x][Building[i].y/16+y]="0";
          Boom[j].type=0;
          Boom[j].npic = (boomcount==1)?(0):(random(11)-10);
        }
        Building[i].alive=false;
      }
      if(Building[i].type==3){
        int bx = (Building[i].x+Building[i].sqleft*16+Building[i].sqwidth*8)/16,
            by = (Building[i].y+Building[i].sqtop*16+Building[i].sqheight*8)/16,
            ux, uy;
        if(Building[i].atkunt==-1){
          Building[i].atkpos=0;
          Building[i].npic=0;
          for(j=0;j<MAX_UNIT;j++)
            if(Unit[j].player==0&&Unit[j].alive&&Unit[j].visible){
              ux = (Unit[j].x+Unit[j].centerx)/16;
              uy = (Unit[j].y+Unit[j].centery)/16;
              if(sqrt((bx-ux)*(bx-ux)+(by-uy)*(by-uy))<Building[i].atkr){
                Building[i].atking=true;
                Building[i].atkunt=j;
                if((Unit[j].atked&&DistUB(i+10000,j)<DistUB(Unit[j].atkedby,j))||
                                !Unit[j].atked){
                  Unit[j].atked=true;
                  Unit[j].atkedby=i+10000;
                }
                break;
              }
            }
        }
        else{
          j=Building[i].atkunt;
          ux = (Unit[j].x+Unit[j].centerx)/16;
          uy = (Unit[j].y+Unit[j].centery)/16;
          if(sqrt((bx-ux)*(bx-ux)+(by-uy)*(by-uy))>Building[i].atkr||
                  Unit[j].player!=0||!Unit[j].alive||!Unit[j].visible){
            Unit[Building[i].atkunt].atked=false;
            Building[i].atkunt=-1;
            Building[i].atking=false;
            Building[i].atkpos=0;
            Building[i].npic=0;
          }
        }
        if(Building[i].atkunt!=-1){
          Building[i].turn=-1;
          j=Building[i].atkunt;
          float x=Unit[j].x+Unit[j].centerx-Building[i].x-Building[i].sqleft*16-
                          Building[i].sqwidth*8,
                y=Unit[j].y+Unit[j].centery-Building[i].y-Building[i].sqtop*16-
                          Building[i].sqheight*8;
          int sval=41, lval=241, A=(y==0)?(99999999):(x/y*100);
          if(y==0)y=0.1;
          if(A>=-sval && A<=sval          && y<=0) Building[i].turn=0.1;
          if(A>=-lval && A<=-sval && x>=0 && y<=0) Building[i].turn=1.1;
          if(abs(A)>=lval         && x>=0)         Building[i].turn=2.1;
          if(A>=sval  && A<=lval  && x>=0 && y>=0) Building[i].turn=3.1;
          if(A>=-sval && A<=sval          && y>=0) Building[i].turn=4.1;
          if(A>=-lval && A<=-sval && x<=0 && y>=0) Building[i].turn=5.1;
          if(abs(A)>=lval         && x<=0)         Building[i].turn=6.1;
          if(A>=sval  && A<=lval  && x<=0 && y<=0) Building[i].turn=7.1;
          if(Building[i].turn<=-0.9&&Building[i].turn>=-1.1)Building[i].turn=0.1;
        }
        else{
          Building[i].turn+=0.25;
          if((int)Building[i].turn>=8)Building[i].turn=0.1;
          Building[i].turn = int(Building[i].turn/0.25)*0.25;
        }
        if(Building[i].atking){
          Building[i].atkpos++;
          if(Building[i].atkpos>=10) Building[i].atkpos=0;
          if(Building[i].atkpos==1||Building[i].atkpos==3||Building[i].atkpos==5){
            sndPlaySound("Sound\\Special\\Atk1",SND_ASYNC+SND_NODEFAULT);
            j=Building[i].atkunt;
            Building[i].npic=1;
            Unit[j].hits-=Building[i].atkfrc;
            if(Unit[j].hits<=0){
              Unit[j].hits=0;
              Unit[j].dying=true;
            }
          }
          else
            Building[i].npic=0;
        }
      }
    ...
    

И вот такая простыня — на две с лишним тысячи строк.

Юниты:

  • 0 SCV: собирает ресурсы, строит здания, производится в Command Center. И минералы, и газы добываются в Refinery.
  • 1 Vulture: боевой юнит противника, стреляет лазером, скоростной, производится в Barracks.
  • 2 Wraith: летающий юнит игрока, не атакует, летает, скоростной, производит высадку Dinosaur, производится в Barracks.
  • 3 Car: ничего не делающий юнит игрока.
  • 5 Robot: боевой юнит игрока, стрелят яичницей, медленный, производится в Barracks.
  • 6 Dinosaur: боевой юнит игрока, не атакует, мало жизней, при взрыве сносит к чертям все юниты в радиусе взрыва.

Здания:

  • 0 Command Center: производит SCV, сюда они носят добытые ресурсы.
  • 1 Refinery: место добычи ресурсов с помощью SCV.
  • 2 Barracks: место постройки боевых юнитов.
  • 3 Gun: боевое здание противника.

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

  • Стрелки: перемещение по карте
  • H: следование за выбранным юнитом
  • P: пауза
  • Delete: удаление юнита
  • C+U+F12: чит-код для создания юнита. Потом нужно ввести на нумпаде две цифры — код юнита и (опционально) две цифры — номер игрока. Например, C+U+F12, 0500 переключит в режим создания роботов игроку с помощью мыши. Чтобы отключить, снова нажать C+U+F12.

Подсказки:

  • Защищайте Refinery! В первую очередь противник будет сносить это здание.
  • Если не успели построить армию, а противник уже напал, то сброшенные с самолёта динозавры вас спасут.
  • Стройте много, стройте быстро.

Скачать игру DeathCraft (5.3 MB, сорцы и исполняемый файл прилагаются, запускать в режиме совместимости)
Ещё я собирался написать про платформенную аркаду и новую версию стратегии реального времени (обе — уже с исключительно своей графикой). Но я утомился, да и шут знает, нужно ли писать — если эта статья окажется читателям Хабра неинтересной, то можно не тратить время впустую.
За сим откланяюсь. А вы, уважаемые читатели, что писали в школьные и студенческие годы?
UPD 1: Несколько читателей сообщило, что вылетают ошибки «No MCI device open». Перезалил файл. Теперь при запуске можно указать аргумент --no-video, тогда видео-файл грузиться не будет.
UPD 2: Другой вариант решения проблемы: установить кодек с XVid.org.