Русский
Русский
English
Статистика
Реклама

Electronic arts

Код игры Command amp Conquer баги из 90-х. Том второй

13.07.2020 10:06:40 | Автор: admin
image1.png

Американская компания Electronic Arts Inc (EA) выложила в открытый доступ исходный код игр Command & Conquer: Tiberian Dawn и Command & Conquer: Red Alert. В исходном коде было обнаружено несколько десятков ошибок с помощью анализатора PVS-Studio, поэтому встречайте продолжение описания найденных дефектов.

Введение


Command & Conquer серия компьютерных игр в жанре стратегии в реальном времени. Первая игра серии была выпущена в 1995 году. Исходный код игр опубликовали вместе с выпуском коллекции Command & Conquer Remastered.

Для поиска ошибок в коде использовался анализатор PVS-Studio. Это инструмент для выявления ошибок и потенциальных уязвимостей в исходном коде программ, написанных на языках С, C++, C# и Java.

Ссылка на первый обзор ошибок: "Игра Command & Conquer: баги из 90-х. Том первый".

Ошибки в условиях


V583 The '?:' operator, regardless of its conditional expression, always returns one and the same value: 3072. STARTUP.CPP 1136

void Read_Setup_Options( RawFileClass *config_file ){  ....  ScreenHeight = ini.Get_Bool("Options", "Resolution", false) ? 3072 : 3072;  ....}

Оказывается, на некоторые настройки пользователи не могли повлиять. Точнее они что-то делали, но из-за того, что тернарный оператор всегда возвращает одно значение, по факту ничего не менялось.

V590 Consider inspecting the 'i < 8 && i < 4' expression. The expression is excessive or contains a misprint. DLLInterface.cpp 2238

// Maximum number of multi players possible.#define MAX_PLAYERS 8 // max # of players we can havefor (int i = 0; i < MAX_PLAYERS && i < 4; i++) {  if (GlyphxPlayerIDs[i] == player_id) {    MultiplayerStartPositions[i] = XY_Cell(x, y);  }}

Из-за неправильного цикла не задаётся позиция для всех игроков. С одной стороны, мы видим константу MAX_PLAYERS 8 и предполагаем, что это максимальное количество игроков. С другой мы видим условие i < 4 и оператор &&. Таким образом, цикл никогда не делает 8 итераций. Скорее всего, на начальном этапе разработки программист не использовал константы, а когда начал забыл удалить старые числа из кода.

V648 Priority of the '&&' operation is higher than that of the '||' operation. INFANTRY.CPP 1003

void InfantryClass::Assign_Target(TARGET target){  ....  if (building && building->Class->IsCaptureable &&    (GameToPlay != GAME_NORMAL || *building != STRUCT_EYE && Scenario < 13)) {    Assign_Destination(target);  }  ....}

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

  • V648 Priority of the '&&' operation is higher than that of the '||' operation. TEAM.CPP 456
  • V648 Priority of the '&&' operation is higher than that of the '||' operation. DISPLAY.CPP 1160
  • V648 Priority of the '&&' operation is higher than that of the '||' operation. DISPLAY.CPP 1571
  • V648 Priority of the '&&' operation is higher than that of the '||' operation. HOUSE.CPP 2594
  • V648 Priority of the '&&' operation is higher than that of the '||' operation. INIT.CPP 2541

V617 Consider inspecting the condition. The '((1L << STRUCT_CHRONOSPHERE))' argument of the '|' bitwise operation contains a non-zero value. HOUSE.CPP 5089

typedef enum StructType : char {  STRUCT_NONE=-1,  STRUCT_ADVANCED_TECH,  STRUCT_IRON_CURTAIN,  STRUCT_WEAP,  STRUCT_CHRONOSPHERE, // 3  ....}#define  STRUCTF_CHRONOSPHERE (1L << STRUCT_CHRONOSPHERE)UrgencyType HouseClass::Check_Build_Power(void) const{  ....  if (State == STATE_THREATENED || State == STATE_ATTACKED) {    if (BScan | (STRUCTF_CHRONOSPHERE)) {  // <=      urgency = URGENCY_HIGH;    }  }  ....}

Чтобы проверить, выставлены ли определённые биты в переменной, следует использовать оператор &, а не |. Из-за опечатки в этом фрагменте кода получилось всегда истинное условие.

V768 The enumeration constant 'WWKEY_RLS_BIT' is used as a variable of a Boolean-type. KEYBOARD.CPP 286

typedef enum {  WWKEY_SHIFT_BIT = 0x100,  WWKEY_CTRL_BIT  = 0x200,  WWKEY_ALT_BIT   = 0x400,  WWKEY_RLS_BIT   = 0x800,  WWKEY_VK_BIT    = 0x1000,  WWKEY_DBL_BIT   = 0x2000,  WWKEY_BTN_BIT   = 0x8000,} WWKey_Type;int WWKeyboardClass::To_ASCII(int key){  if ( key && WWKEY_RLS_BIT)    return(KN_NONE);  return(key);}

Я думаю, в параметре key хотели проверить определённый бит, заданный маской WWKEY_RLS_BIT, но сделали опечатку. Следовало использовать побитовый оператор &, а не &&, чтобы проверить код клавиши.

Подозрительное форматирование


V523 The 'then' statement is equivalent to the 'else' statement. RADAR.CPP 1827

void RadarClass::Player_Names(bool on){  IsPlayerNames = on;  IsToRedraw = true;  if (on) {    Flag_To_Redraw(true);//    Flag_To_Redraw(false);  } else {    Flag_To_Redraw(true);   // force drawing of the plate  }}

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

Точно таких же мест нашлось ещё два:

  • V523 The 'then' statement is equivalent to the 'else' statement. CELL.CPP 1792
  • V523 The 'then' statement is equivalent to the 'else' statement. RADAR.CPP 2274

V705 It is possible that 'else' block was forgotten or commented out, thus altering the program's operation logics. NETDLG.CPP 1506

static int Net_Join_Dialog(void){  ....  /*...............................................................  F4/SEND/'M' = edit a message  ...............................................................*/  if (Messages.Get_Edit_Buf()==NULL) {    ....  } else  /*...............................................................  If we're already editing a message and the user clicks on  'Send', translate our input to a Return so Messages.Input() will  work properly.  ...............................................................*/  if (input==(BUTTON_SEND | KN_BUTTON)) {    input = KN_RETURN;  }  ....}

Из-за большого комментария разработчик не увидел выше недописанный условный оператор. Оставшееся ключевое слово else образует с условием ниже конструкцию else if, что, скорее всего, является изменением изначальной логики.

V519 The 'ScoresPresent' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 539, 541. INIT.CPP 541

bool Init_Game(int , char *[]){  ....  ScoresPresent = false;//if (CCFileClass("SCORES.MIX").Is_Available()) {    ScoresPresent = true;    if (!ScoreMix) {      ScoreMix = new MixFileClass("SCORES.MIX");      ThemeClass::Scan();    }//}

Ещё один потенциальный дефект из-за незаконченного рефакторинга. Теперь непонятно, переменная ScoresPresent должна иметь значение true, или всё-таки false.

Ошибки освобождения памяти


V611 The memory was allocated using 'new T[]' operator but was released using the 'delete' operator. Consider inspecting this code. It's probably better to use 'delete [] poke_data;'. CCDDE.CPP 410

BOOL Send_Data_To_DDE_Server (char *data, int length, int packet_type){  ....  char *poke_data = new char [length + 2*sizeof(int)]; // <=  ....  if(DDE_Class->Poke_Server( .... ) == FALSE) {    CCDebugString("C&C95 - POKE failed!\n");    DDE_Class->Close_Poke_Connection();    delete poke_data;                                  // <=    return (FALSE);  }  DDE_Class->Close_Poke_Connection();  delete poke_data;                                    // <=  return (TRUE);}

Анализатор обнаружил ошибку, связанную с тем, что память может выделяется и освобождаться несовместимыми между собой способами. Для освобождения памяти, выделенной под массив, следовало использовать оператор delete[], а не delete.

Таких мест нашлось несколько, и все они понемногу вредят работающему приложению (игре):

  • V611 The memory was allocated using 'new T[]' operator but was released using the 'delete' operator. Consider inspecting this code. It's probably better to use 'delete [] poke_data;'. CCDDE.CPP 416
  • V611 The memory was allocated using 'new T[]' operator but was released using the 'delete' operator. Consider inspecting this code. It's probably better to use 'delete [] temp_buffer;'. INIT.CPP 1302
  • V611 The memory was allocated using 'new T[]' operator but was released using the 'delete' operator. Consider inspecting this code. It's probably better to use 'delete [] progresspalette;'. MAPSEL.CPP 795
  • V611 The memory was allocated using 'new T[]' operator but was released using the 'delete' operator. Consider inspecting this code. It's probably better to use 'delete [] grey2palette;'. MAPSEL.CPP 796
  • V611 The memory was allocated using 'new T[]' operator but was released using the 'delete' operator. Consider inspecting this code. It's probably better to use 'delete [] poke_data;'. CCDDE.CPP 422
  • V611 The memory was allocated using 'new T[]' operator but was released using the 'delete' operator. Consider inspecting this code. It's probably better to use 'delete [] temp_buffer;'. INIT.CPP 1139

V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. ENDING.CPP 254

void GDI_Ending(void){  ....  void * localpal = Load_Alloc_Data(CCFileClass("SATSEL.PAL"));  ....  delete [] localpal;  ....}

Операторы delete и delete[] разделены неслучайно. Они выполняют разную работу по очистке памяти. А при использовании нетипизированного указателя компилятор не знает, на какой тип данных ведёт указатель. В стандарте языка C++ поведение компилятора неопределённо.

Такого рода тоже нашёлся целый ряд предупреждений анализатора:

  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. HEAP.CPP 284
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. INIT.CPP 728
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. MIXFILE.CPP 134
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. MIXFILE.CPP 391
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. MSGBOX.CPP 423
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. SOUNDDLG.CPP 407
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. BUFFER.CPP 126
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. BUFF.CPP 162
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. BUFF.CPP 212
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. BFIOFILE.CPP 330
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. EVENT.CPP 934
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. HEAP.CPP 318
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. INIT.CPP 3851
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. MIXFILE.CPP 130
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. MIXFILE.CPP 430
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. MIXFILE.CPP 447
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. MIXFILE.CPP 481
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. MSGBOX.CPP 461
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. QUEUE.CPP 2982
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. QUEUE.CPP 3167
  • V772 Calling a 'delete' operator for a void pointer will cause undefined behavior. SOUNDDLG.CPP 406

V773 The function was exited without releasing the 'progresspalette' pointer. A memory leak is possible. MAPSEL.CPP 258

void Map_Selection(void){  ....  unsigned char *grey2palette    = new unsigned char[768];  unsigned char *progresspalette = new unsigned char[768];  ....  scenario = Scenario + ((house == HOUSE_GOOD) ? 0 : 14);  if (house == HOUSE_GOOD) {    lastscenario = (Scenario == 14);    if (Scenario == 15) return;  } else {    lastscenario = (Scenario == 12);    if (Scenario == 13) return;  }  ....}

"Если вообще не освобождать память, то точно не ошибусь в выборе оператора!" возможно, подумал программист.

image2.png

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

Разное


V570 The 'hdr->MagicNumber' variable is assigned to itself. COMBUF.CPP 806

struct CommHdr {  unsigned short MagicNumber;  unsigned char Code;  unsigned long PacketID;} *hdr;void CommBufferClass::Mono_Debug_Print(int refresh){  ....  hdr = (CommHdr *)SendQueue[i].Buffer;  hdr->MagicNumber = hdr->MagicNumber;  hdr->Code = hdr->Code;  ....}

Два поля структуры CommHdr инициализируются собственными значениями. По-моему, бессмысленная операция, но выполняется она много раз:

  • V570 The 'hdr->Code' variable is assigned to itself. COMBUF.CPP 807
  • V570 The 'hdr->MagicNumber' variable is assigned to itself. COMBUF.CPP 931
  • V570 The 'hdr->Code' variable is assigned to itself. COMBUF.CPP 932
  • V570 The 'hdr->MagicNumber' variable is assigned to itself. COMBUF.CPP 987
  • V570 The 'hdr->Code' variable is assigned to itself. COMBUF.CPP 988
  • V570 The 'obj' variable is assigned to itself. MAP.CPP 1132
  • V570 The 'hdr->MagicNumber' variable is assigned to itself. COMBUF.CPP 910
  • V570 The 'hdr->Code' variable is assigned to itself. COMBUF.CPP 911
  • V570 The 'hdr->MagicNumber' variable is assigned to itself. COMBUF.CPP 1040
  • V570 The 'hdr->Code' variable is assigned to itself. COMBUF.CPP 1041
  • V570 The 'hdr->MagicNumber' variable is assigned to itself. COMBUF.CPP 1104
  • V570 The 'hdr->Code' variable is assigned to itself. COMBUF.CPP 1105
  • V570 The 'obj' variable is assigned to itself. MAP.CPP 1279

V591 Non-void function should return a value. HEAP.H 123

int FixedHeapClass::Free(void * pointer);template<class T>class TFixedHeapClass : public FixedHeapClass{  ....  virtual int Free(T * pointer) {FixedHeapClass::Free(pointer);};};

В функции Free класса TFixedHeapClass нет оператора return. Самое интересное, что у вызываемой функции FixedHeapClass::Free возвращаемое значение тоже типа int. Скорее всего, программист просто забыл написать оператор return и теперь функция возвращает непонятное значение.

V672 There is probably no need in creating the new 'damage' variable here. One of the function's arguments possesses the same name and this argument is a reference. Check lines: 1219, 1278. BUILDING.CPP 1278

ResultType BuildingClass::Take_Damage(int & damage, ....){  ....  if (tech && tech->IsActive && ....) {    int damage = 500;    tech->Take_Damage(damage, 0, WARHEAD_AP, source, forced);  }  ....}

Параметр damage передаётся по ссылке. Следовательно, в теле функции ожидается изменение значений этой переменной. Но в одном месте разработчик объявил переменную с таким же именем. Из-за этого значение 500 сохранится в локальную переменную damage, а не параметр функции. Возможно, задумывалось другое поведение.

Ещё одно такое место:

  • V672 There is probably no need in creating the new 'damage' variable here. One of the function's arguments possesses the same name and this argument is a reference. Check lines: 4031, 4068. TECHNO.CPP 4068

V762 It is possible a virtual function was overridden incorrectly. See first argument of function 'Occupy_List' in derived class 'BulletClass' and base class 'ObjectClass'. BULLET.H 90

class ObjectClass : public AbstractClass{  ....  virtual short const * Occupy_List(bool placement=false) const; // <=  virtual short const * Overlap_List(void) const;  ....};class BulletClass : public ObjectClass,                    public FlyClass,                    public FuseClass{  ....  virtual short const * Occupy_List(void) const;                 // <=  virtual short const * Overlap_List(void) const {return Occupy_List();};  ....};

Анализатор обнаружил потенциальную ошибку при переопределении виртуальной функции Occupy_List. Это может приводить к вызову не тех функций в рантайме.

Ещё несколько подозрительных мест:

  • V762 It is possible a virtual function was overridden incorrectly. See qualifiers of function 'Ok_To_Move' in derived class 'TurretClass' and base class 'DriveClass'. TURRET.H 76
  • V762 It is possible a virtual function was overridden incorrectly. See fourth argument of function 'Help_Text' in derived class 'HelpClass' and base class 'DisplayClass'. HELP.H 55
  • V762 It is possible a virtual function was overridden incorrectly. See first argument of function 'Draw_It' in derived class 'MapEditClass' and base class 'HelpClass'. MAPEDIT.H 187
  • V762 It is possible a virtual function was overridden incorrectly. See first argument of function 'Occupy_List' in derived class 'AnimClass' and base class 'ObjectClass'. ANIM.H 80
  • V762 It is possible a virtual function was overridden incorrectly. See first argument of function 'Overlap_List' in derived class 'BulletClass' and base class 'ObjectClass'. BULLET.H 102
  • V762 It is possible a virtual function was overridden incorrectly. See qualifiers of function 'Remap_Table' in derived class 'BuildingClass' and base class 'TechnoClass'. BUILDING.H 281
  • V762 It is possible a virtual function was overridden incorrectly. See fourth argument of function 'Help_Text' in derived class 'HelpClass' and base class 'DisplayClass'. HELP.H 58
  • V762 It is possible a virtual function was overridden incorrectly. See first argument of function 'Overlap_List' in derived class 'AnimClass' and base class 'ObjectClass'. ANIM.H 90

V763 Parameter 'coord' is always rewritten in function body before being used. DISPLAY.CPP 4031

void DisplayClass::Set_Tactical_Position(COORDINATE coord){  int xx = 0;  int yy = 0;  Confine_Rect(&xx, &yy, TacLeptonWidth, TacLeptonHeight,    Cell_To_Lepton(MapCellWidth) + GlyphXClientSidebarWidthInLeptons,    Cell_To_Lepton(MapCellHeight));  coord = XY_Coord(xx + Cell_To_Lepton(MapCellX), yy + Cell_To_Lepton(....));  if (ScenarioInit) {    TacticalCoord = coord;  }  DesiredTacticalCoord = coord;  IsToRedraw = true;  Flag_To_Redraw(false);}

Параметр coord сразу перезаписывается в теле функции. Старое значение не использовалось. Это очень подозрительно, когда у функции есть аргументы, а она от них не зависит. А тут ещё координаты какие-то передают.

И это место стоит проверить:

  • V763 Parameter 'coord' is always rewritten in function body before being used. DISPLAY.CPP 4251

V507 Pointer to local array 'localpalette' is stored outside the scope of this array. Such a pointer will become invalid. MAPSEL.CPP 757

extern "C" unsigned char *InterpolationPalette;void Map_Selection(void){  unsigned char localpalette[768];  ....  InterpolationPalette = localpalette;  ....}

В коде игр присутствует очень много глобальных переменных. Вероятно, в те времена это был распространённый подход к написанию кода. Но сейчас он считается плохим и даже опасным.

В указатель InterpolationPalette сохраняется локальный массив localpalette, который станет невалидным после выхода из функции.

Ещё парочка опасных мест:

  • V507 Pointer to local array 'localpalette' is stored outside the scope of this array. Such a pointer will become invalid. MAPSEL.CPP 769
  • V507 Pointer to local array 'buffer' is stored outside the scope of this array. Such a pointer will become invalid. WINDOWS.CPP 458

Заключение


Как я уже писал в первом отчёте, будем надеяться, что новые проекты Electronic Arts более качественные. Вообще, разработчики игр активно приобретают PVS-Studio. Сейчас бюджеты игр достаточно велики, поэтому лишние расходы на исправление багов в продакшене никому не нужны. А исправление ошибки на раннем этапе написания кода практически не отнимает время и другие ресурсы.

Приглашаем на наш сайт скачать и попробовать PVS-Studio на всех проектах.


Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Svyatoslav Razmyslov. The Code of the Command & Conquer Game: Bugs from the 90's. Volume two.
Подробнее..

Перевод А мог побороться с CoD История создания шутера Black

04.01.2021 16:19:24 | Автор: admin

На закате эры PS2, студия Criterion, известная своим мастерством в гоночных играх, сделала ставку на другой популярный жанр FPS. Редакция журнала Retro Gamer расспросила бывшего руководителя студии Алекса Уорда о создании популярного консольного шутера. Зрелищный боевик от первого лица показал всё на что способно железо PS2. Здесь есть разрушаемое окружение, огромные и детализированные локации, а также потрясающие визуальные эффекты. Тем не менее разработчик рисковал, выпуская накануне старта нового поколения игру для прошлого и без сетевой игры

Сейчас в это трудно поверить, но раньше играть в шутеры от первого лица на консолях было не комфортно. Хороший шутан на приставках считался лишь исключением из правил, поэтому такая классика, как GoldenEye, TimeSplitters и Halo, пользуются всеобщим почтением. После Halo разработчики и издатели поняли, что не только начинка систем вытягивает жанр, но на этом ещё можно заработать. Шестое поколение (PS2-Xbox-Gamecube) подарило игрокам множество отличных FPS. Геймер мог испытать острые ощущений в TimeSplitters 2, окунуться в мрачную атмосферу Chronicles Of Riddick: Escape From Butcher Bay, сыграть в историческую Medal Of Honor или опробовать переосмысление серии Metroid Prime. Этот период стал самым значимым в формировании консольных FPS.

К всему перечисленному стоит добавить появившийся на закате поколения Black боевик от одной из самых талантливых студий того времени. Мы тогда работали над Burnout 3, а Black представили на закрытом показе шоу E3, вспоминает Алекс Уорд, бывший глава Criterion и нынешний основатель собственной студию Three Fields Entertainment. Мы полагали, что хорошо разбираемся в архитектуре PS2 и способны создать отличный шутер. Единственным конкурентом была Medal Of Honor, которую плохо оптимизировали на PS2, что добавило нам уверенности. Мы построили простой уровень, который показали за закрытыми дверьми на E3 журналистам. Помню как бегал между двумя залами: в одном была презентации Burnout, а в другом Black. Это было весело, хоть и порядком меня вымотало

В тот год мы хотели показать всем на что способна Criterion! Вдобавок студия была в процессе покупки EA. Когда мы поехали на выставку с Black, новый издатель предупредил, что не стоит показывать там прототип. Но я подумал, что сделка может сорваться, поэтому не стоит упускать возможность показать на E3 ещё один проект. Считаю, надо устраивать выставку два раза в году, чтобы разработчики показали идеи, а потом вернулись с альфа-версией. Но вместо этого, приходится кранчить, дабы представить на выставке ранний геймплей.

Кино как источник вдохновения

В основе игры концепции разрушения окружающей среды (по примеру Red Faction), напряжённые перестрелки и зрелищные взрывы как в голливудских блокбастерах. Такой проект станет амбициозным для любой студии, что уж говорить о Criterion, которая была известна лишь гоночными играми. На стадии прототипа над Black работала очень маленькая команда, рассказывает Алекс. Мы раздумывали о применении оружия, и сможем ли это воплотить. Потом нас купила EA, но издатель не смог бы отменить проект, потому что мы уже показали игру и продолжали делиться новостями о разработке. После завершения работы над Burnout 3, команду ответственную за гонку разделили: одна половина приступила к Burnout Revenge, а другая занялась Black. В студии было принято начинать разработку с создания рипоматики (раскадровка) это собранные вместе отрывки из фильмов, чтобы создать представление о будущей игре. Алекс показал эту нарезку, в ней есть сцены из таких фильмов как Хищник, Солдат Джейн, В осаде, В тылу врага и Три короля. Картины формируют тон и ощущения создаваемой игры задолго до начала реальной разработки.

На самом деле замысел возник гораздо раньше. Идея возникла, когда мы делали первый Bumout, объясняет Алекс. Мы хотели сделать стрелялку, но возникла проблема: в кого игрок будет стрелять? Я решил, что действие надо перенести в Россию. Потому что любил пересматривать картину Святой на LaserDisc (предшественник DVD) фильм Филлипа Нойса с Вэлом Килмером (дешёвая клюква, если захотите посмеяться, можете глянуть). Я подумал, что если в кинематографе можно делать русских злодеями, значит они станут противниками и в нашей игре. В фильмах времён холодной войны часто строили на этом сюжет.

Я купил через интернет русские наручные часы, потому что являюсь поклонником трилогии Назад в будущее. Во второй части на рубашке Дока изображены ковбои и поезда это отсылка к следующему фильму. Таким же образом русские часы намекали на будущий проект студии. Глупо конечно, но я носил их около года, засветившись на всех интервью по Burnout 3.

Чувствую, что решение сделать нас злодеями, накалит многих читателей, поэтому предлагаю ненадолго отвлечься и почитать про создание звука в игре.

Вдохновляясь боевиками, разработчики брали звуковые эффекты для оружия из известных фильмов. MP5 Джона Макклейна из картины Крепкий орешек, пистолет Джека Бауэра в сериале 24 и Узи героя Арнольда Шварценеггера из Правдивой лжи. Понимая, что в хаосе перестрелки все звуки смешиваются и начинают резать слух, звукорежиссёры придумали концепцию под названием хор орудий. Традиционно в шутерах каждой модели оружия назначается отдельный звук. В Black каждому врагу присвоен собственный звук выстрела, подобно тому, как каждый член хора наделён собственным голосом. Например, стреляют три врага: одному их них присвоят низкий голос, другому средний, а третьему высокий. Это позволяет добиться гармонии и обеспечивать отличный звук в игре. За что проект номинировали на награду BAFTA Video Games Awards 2006 в категории лучший звук. Также совместно с Burnout Revenge шутер получил награду Best Art & Sound на конкурсе Develop Industry Excellence Awards 2006.

А теперь вернёмся к интервью с создателем.

Criterion переоценила свои силы, взявшись за разработку сразу двух игр. Пришлось попотеть. Закончив Burnout 3, мы сразу перешли к Revenge, вспоминает Алекс. Команда трудилась семь дней в неделю. Нас недавно купила EA и мы хотели показать всё, на что способны. Black надо было закончить в январе, а это означало, что предстоит работать все новогодние праздники. Мы сидели в офисе в канун Рождества, тестируя проект. Кажется, были готовы шесть уровней. Но код сыроват там всё было сломано. Мы с Крейгом Салливаном (ведущий дизайнер) сидели ухватившись за головы. Поспешно отправив команду домой, мы рассчитывали, что проблем не возникнет. Думали, мол, быстро пройдём пять уровней и по домам В итоге дошли до четвёртого, и здесь игра ломалась! И это не первая проблема, с которой команда сталкивалась.

Создание повествования

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

Здесь команда столкнулась с трудной задачей. У нас не было возможности снимать ролики самостоятельно. Займись мы этим сами, пришлось бы нанимать актёров и всю съёмочную команду. Но мы делаем игру в офисе и там нет места для съёмочной площадки! Поэтому требовалась помощь со стороны, Алекс продолжает объяснять, как они решили задачу. В команде был Нило Родис, с опытом работы в киноиндустрии. Он познакомил меня с художником-постановщиком Джозефом Ходжесом, который работал над сериалом 24. Бюджет разработки не потянул бы киносъёмки, поэтому мы позвонили ему и попросили сделать одолжение. Джозеф согласился.

Criterion сначала экспериментировала с абстрактными роликами и закадровым голосом, но результат не впечатлял. Требовалось другое решение. Джо сказал: Я сниму это завтра вживую на площадке "24" до того как начнутся съёмки сериала. Они даже об этом не узнают! Я спросил насчёт костюмов, а он ответил, что договорится и об этом. Актёрам сказали явиться в 5 утра на съёмочную площадку в Чатсуорте, Лос-Анджелес. Он отснял сцены в комнате для допросов, которая часто фигурирует в сериале. Съёмки проходили тайно. Джозеф гений, мы остались довольны отснятым материалом.

Удивительно, но игра таки попала в сериал! В одном из эпизодов персонаж шоу играет в видеоигру там лишь пара кадров с геймплеем, но я знаю, на каком уровне он находится, потому что помню, как подписывал разрешение на показ игры в сериале, ухмыляется Алекс. В конечном итоге Джон Мур (режиссёр В тылу врага) и кинокомпания 20th Century Fox решили снять фильм по мотивам Black. Алекс рассказал о встрече с продюсером Хищника Джоном Дэвисом и Алексом Янгом, продюсировавшим Команду А. Подготовка к съёмкам уже началась, как и создание второй части игры, но оба проекта не увидели свет. Fox была заинтересована в создании фильма. Я не знаю подробностей и причин отмены картины, но немного расскажу про сиквел игры.

Концепт-артыКонцепт-арты

Уйти в закат на волне популярности

Предстояло осваивать архитектуру некстген-консолей, поэтому мы отказались от решения разбивать сильную команду на две более слабые. Вновь собрав сотрудников вместе, все переключились на Burnout Paradise, поэтому разработка Black 2 прекратилась. Мы работали над ней около полугода. Был готов прототип с несколькими уровнями, а также модели персонажей. Это отняло много времени, усилий и требовало больших вложений реши мы продолжать работу. Выбор стал между Burnout и Black выжить мог только один проект. Оглядываясь назад, считаю такое решение правильным. Black стала успешной игрой и полюбилась многим, но фанатов Burnout гораздо больше нельзя оставлять их ни с чем

Бывший сотрудник Criterion называет другие причины закрытия проекта. Дизайнер Стюарт Блэк в интервью рассказал, что Black 2 отменили из-за разногласий с Electronic Arts. Стюарт и другие разработчики из Criterion ушли в Codemasters, где работали над духовным наследником Bodycount. Игру выпустили для Xbox 360 и PlayStation 3 в 2011 году. Боевик получил смешанные отзывы и не достиг высоких показателей продаж. В результате Codemasters закрыла студию в Гилфорде.

Но вернёмся к Алексу Уорду и его студии Three Fields Entertainment, основанной вместе с другими сотрудниками Criterion: сооснователем Фионой Сперри и разработчиком Полом Россом. Компания разрабатывает игры с 2014 года и уже выпустила по две части гоночных аркад в стиле Burnout Danger Zone и Dangerous Driving. Дела у студии идут хорошо, поэтому можно ожидать, что разработчики вернутся к идее создания зрелищного боевика с оглядкой на кинематограф Но это не точно.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru