Game Maker Language и так называемые Настоящие Языки Программирования
Game Maker Language и так называемые Настоящие Языки Программирования
или
Скриптинг против кодинга, разработчики игр против программистов, мёд против пчёл
а также
Среда разработки Game Maker против MS Visual Studio/SharpDevelop/CodeBlocks/Eclipse и, кажется, какиЕ-то ещё IDE, которыми я пользовался мало, и названия которых я забыл
Важное замечание:
в этой статье я говорю о том GML, который был в Game Maker 8.1, потому что к GML в Game Maker Studio не относится добрая половина этого текста (причём та половина, которая описывает его уникальные возможности — уже, как понимаете, бывшие). Также я говорю об IDE той же версии 8.1, которая с 2011 года изменилась по большей части в сторону кросс-платформенных настроек, поддержки костной анимации и чего-то совсем уже отстранённого от её настоящих проблем. Таковые я в этом тексте упомяну в деталях, дабы попытаться развеять несуразное распространённое мнение о том, что в GM уже «всё за вас сделано».
Прежде всего, хочу заявить, что GML — весьма недооценённый язык программирования для создания игр, использования всей мощности которого я не видел ещё ни в чьём исполнении. Я не имею в виду, что он позволяет сделать любую игру за три строки кода, и также я не имею в виду, что его синтаксис позволяет программировать игру как-то особо быстро. Однако посмотрите, что есть в GML:
1. Интерпретатор кода на лету
Если вам заранее неизвестно, какой конкретно код вам нужно будет выполнить в какой-либо момент игры, вы можете собирать его динамически, прописав соответствующие инструкции, и выполнить его через execute_string. Вы также можете читать и выполнять код из внешних файлов, и даже вовсе не хранить никакого кода внутри проекта. С таким подходом любой файл с GML-кодом является по смыслу чем-то вроде DLL, только не надо возиться ни с заголовочными файлами (C/C++), ни со сборками (C#). DLL при играх по большей части используются для громоздких и сложных вычислений, или рендеринга, а GML для вычислений не приспособлен, и справляется с ними довольно медленно. Тем не менее, для своих внутренних задач такие фрагменты повторно используемого кода вполне могли бы быть полезны.
Но есть и ещё один важный аспект — игра, которая читает свой код из внешних файлов, легчайше поддаётся созданию модов к ней. То есть если игрок знает GML, он может легчайше создать своё дополнение к совершенно чужой игре, закинув пару спрайтов и звуков в соответствующую папку, и написав некоторое количество GML кода. Если же игрок сам делает игры на GML, он может вообще запустить любой объект своей любой игры в игру, в которую он играет. Естественно, его объекты игровой логики будут взаимодействовать только с его же собственными объектами и понятиями, но если обе игры одного жанра, то поменять имена в коде не составляет большого труда.
Вы слышите, что я говорю? Если бы люди сообща использовали GML таким образом, они могли бы совместить все существующие скролл-шутеры, платформеры, стратегии любых видов, аркады, и так далее (естественно, каждый жанр со своим жанром), создав некие супер-игры каждого из жанров, в которых можно играть за всех игроков и побеждать всех врагов и боссов, перемешивая между собой всё оружие, спецспособности и методы взаимодействия. Что до игр по сети, то поддержку сети пришлось бы написать всего лишь один раз, и все эти супер-игры к тому же были бы ещё и сетевыми. Поддержка джойстика? Та же история. Переназначение клавиш и устройств управления? Та же история. Редактор уровней? Настройки сложности? Система сохранения/загрузки? Ну вы поняли.
Почему этого не было? Так естественно, для этого необходимо, чтобы каждый автор игры сделал такую лазейку, в виде всего лишь одного execute_code при запросе либо просто при старте игры. Таких игр мне не известно ровно ни одной.
Единственный случай, когда я увидел использование внешних файлов с целью описания игровой логики, принадлежит перу того, кто ныне известен как SaintHeiser. Этот проект должен был переиграть механику Megaman Battle Network на новый лад, но так и не взлетел после прототипирования, а сама идея расшаривать код во внешние папки была отброшена навсегда.
2. Механизм добавления событий
С помощью встроенной функции object_event_add вы можете добавить любое событие в любой объект игры. На деле это значит, что ваши объекты по ходу игры могут получать такие возможности взаимодействия, которых изначально в них запрограммировано не было, а в теории — даже и не предсказанные вами изначально, например — полученные в результате симулированного генетического отбора. Кроме «обычного» поведения объекты GM могут иметь мета-поведение, регулирующее правила видоизменения их текущего поведения.
Естественно, этот концепт настолько экспериментален, что его так никто и не использовал, хотя это первое, что приходит в голову, когда узнаёшь про такую возможность GML. Соединяя это с упомянутыми выше возможностями интерпретации кода на лету, получаешь вообще немыслимый комбинационный взрыв вариантов взаимодействия не только внутри одной игры, но внутри серии игр, объединённых неким языком или стандартом взаимодействия. Можно запрограммировать игру, которая является неким хабом игр, и каким-то образом соединяется с другими играми, запущенными в этот момент, которые осуществляют конструктивные либо деструктивные действия по отношению друг к другу. Почему бы не заставить сами игры сражаться друг с другом? Слишком абстрактно? Эх, какие вы зануды.
Используя event_perform_object можно также заставить объект выполнять события совсем другого объекта как свои собственные, а функции variable_*_exists позволяют проверять, существуют ли указанные переменные в текущем контексте выполнения. То есть, если переменной нет, мы её можем всё равно объявить прямо на месте, и она появится.
Кроме того, разбор (парсинг) строк кода для выполнения на лету это громоздкая операция, но можно легко ускорить выполнение кода, расположенного во внешних файлах. Достаточно создать объект и задать ему нужный код, который будет разобран только однократно — при создании соответствующего объекта, и будет выполняться на скорости, равной скорости обычного «родного» объекта проекта. И это кэширование даёт серьёзную разницу.
Однако, на этом все аж два пункта неиспользованных возможностей заканчиваются, и начинается длинный ряд недостатков GML и его родной IDE, которые рано или поздно были с распростёртыми объятиями встречены всеми серьёзными разработчиками на GML, и которые ложатся серьёзным грузом на чашу весов, на другую чашу которых народной молвой поставлен, приклеен, прикручен и приварен аргумент «всё сделано за вас».
Почему GML уродлив?
Нет пользовательских структур данных
Уточняю — «пользовательских», потому что встроенные структуры данных есть, и включают в себя: стек, очередь, список, карту (словарь), очередь с приоритетами и некую сетку, смысла применения которой я так и не понял, и уверен, что никому она в GML не нужна.
Символ точки в коде GML используется (помимо очевидного разделения целой и дробной части вещественных констант) только для адресации переменных объекта. Методов у объекта GM нет, и к событиям объекта обращаться тоже нельзя через точку, но «вызывать» их можно через event_perform_object.
Можно имитировать структуры данных, создавая свои объекты, которые будут хранить ту или иную структуру, и действительно адресовать их составляющие через точку, однако всякий объект GM автоматически содержит в себе все встроенные переменные, касающиеся его отображения на экране, двухмерной физики перемещений и столкновений, использования путей и временных линий событий, и вроде чего-то ещё. Как следствие, такая имитация будет неслыханно тормозить, если этих объектов понадобится много.
Вообще-то огромное количество встроенных переменных и функций выглядит так, словно там вместо прочерков должны быть точки. Возьмём, например, переменные фонов:
background_color Background color for the room.
background_showcolor Whether to clear the window in the background color.
background_visible[0..7] Whether the particular background image is visible.
background_foreground[0..7] Whether the background is actually a foreground.
background_index[0..7] Background image index for the background.
background_x[0..7] X position of the background image.
background_y[0...7] Y position of the background image.
background_htiled[0..7] Whether horizontally tiled.
background_vtiled[0..7] Whether vertically tiled.
background_xscale[0..7] Horizontal scaling factor for the background. (This must be positive; you cannot use a negative value to mirror the background.)
background_yscale[0..7] Vertical scaling factor for the background. (This must be positive; you cannot use a negative value to flip the background.)
background_hspeed[0..7] Horizontal scrolling speed of the background (pixels per step).
background_vspeed[0..7] Vertical scrolling speed of the background (pixels per step).
background_blend[0..7] Blending color to use when drawing the background. A value of c_white is the default. Only available in the Standard Edition!
background_alpha[0..7] Transparency (alpha) value to use when drawing the background. A value of 1 is the normal setting; a value of 0 is completely transparent.
То есть то, что в широко принятой практике было бы адресовано как Background[i].Visible, здесь выглядит несколько мутировавшим — у нас не массив заданных структур, а массив переменных структур без описания самих структур. И все аналогичные «структуры данных» тоже должны использоваться именно так, в виде слабо связанных наборов переменных. Впрочем, даже если бы в GM было иначе, следующая проблема (про точки) никуда бы не делась.
Встроенных типов данных в GML всего два: числа и строки. В связи с этим никогда не возникнет проблем с приведениями целого числа к вещественному, либо наоборот.
null отсутствует как таковой, но есть ключевое слово noone, обозначающее «никакой объект», а instance_exists позволит удостовериться в существовании объекта.
И нет, нельзя передавать в функцию вычисления расстояния объект точки в пространстве, нужно передавать отдельно каждую их координату. Впрочем, это невозможно ещё и потому что в GM нет указателей. При том что можно обращаться к любому объекту через его имя (строку) либо идентификатор экземпляра, указателей никаких не существует.
Нет подсказок после точки
Во всякой современной IDE это есть, а здесь можно писать код в Блокноте — разницы особой не будет, если родные функции GM давно известны. Справедливости ради стоит заметить, что в IDE есть автодополнение для названий встроенных переменных и функций, однако все переменные, которые созданы внутри проекта игры (а ведь это именно то, что меня интересует в первую очередь), ей неизвестны, и это можно понять — в GM их можно объявить в любом месте, и со стороны IDE невозможно предугадать, по какому маршруту выполнения кода пойдёт игра, и особенно — какие локальные переменные делать доступными в скриптах, если неизвестно из каких объектов они будут вызваны.
Ограниченные массивы
Нельзя использовать индексы более 32000, индексов может быть не более двух, и весь массив не может содержать более 1000000 элементов. Вложенных или ступенчатых массивов — не существует здесь (впрочем, это — проблема отсутствия пользовательских структур данных, описанная выше). Это искусственное ограничение обычно не портит никому жизнь, но только до тех пор, пока не понадобится использовать третье измерение (причём опять же, если вам достаточно 30×30×30, это легко, но дальше вас ждёт алхимия), или хранить поток управления игрока без сжатия, длиной более чем на 10:40==640 секунд (32000/50FPS для примера). Отсюда вырастают странные костыли, начиная с практики использования массива длиной в 1000 в качестве трёхмерного, индексируя его через три десятичные цифры от 000 до 999, и до специфической DLL, позволяющей работать с массивами любых размерностей и объёмов. DLL для поддержки массивов, никаких шуток.
Нет механизма исключений
Всё, что вы можете сделать — это программировать всю игру так, чтобы она работала без ошибок. Вы можете отключить уведомления об ошибках, но если вы относитесь к геймдеву серьёзно, одно другого не лучше. Кстати, я был удивлён, когда коммерческий проект, Hotline Miami, швырнул на экран ошибку, выдающую его GM’овское происхождение.
Однако, и обнаружение ошибок в GM имеет свои недостатки. Если перед наступлением ошибки был изменён контекст вызова, отладчику до этого нет никакого дела, он помнит только начальную точку вызова, а дальше — разбирайтесь сами. Для чайников: если вы сделали execute_string, внутри которого ошибка, то отладчик вам укажет только на сам execute_string, начисто забыв про его аргументы, где-то в глубине которых (а возможно и в глубине функции, которая вызвана ещё и дальше оттуда) и находится причина ошибки.
Нет статических объектов
Конструкция вида static.variable (названия объекта и переменной могут быть на самом деле любыми, кроме зарезервированных для обычных названий) позволяет обратиться к первому найденному объекту static, который будет единственным, если его только один раз создать, и будет существовать в пределах всей игры, если ему выставить свойство постоянства (Persistent); так что разницы со статическими объектами C++/C#/Java в этом смысле — никакой. Технически это не проблема GM, но надобность создавать экземпляр объекта, который по сути является статическим и используется как статический — это в терминах ООП некорректно. Впрочем, вне зависимости от проекта существует постоянный контейнер global, в который можно записывать что угодно и который даже не является объектом, и поэтому не пытается куда-то перемещаться или рисоваться на экране, как болванки, имитирующие структуры данных, описанные выше; однако событий содержать не может, так как, повторюсь, не объект — но вообще-то для этого есть скрипты.
Нет именованных параметров у функций (в случае GML — скриптов)
Все параметры именуются через argument0, argument1, … argument15 (в ранних версиях GM их было меньше). Конечно, можно переприсваивать значения переменным со своими названиями, делая что-то в духе x=argument0; y=argument1;, но это чревато снижением производительности при каждом вызове. Почему-то так часто в программировании: либо что-то работает быстро, либо что-то имеет понятный исходный код. И это логично в ассемблере, но не в GML — хватило бы одного стандарта, предписывающего, что скрипт GM должен начинаться со строки вида //name1 name2 name3 … и IDE распознавала бы эти имена, а перед компиляцией просто-напросто заменяла все их на те самые argumentNN, и этой проблемы бы не было.
Нет множественного наследования
Обычное наследование с переопределением событий есть, но — никаких интерфейсов или утиной типизации; каждый объект выполняет только то, что сам содержит, либо наследует от одного конкретного объекта. Если вы хотите максимально разграничить способности в игре, то объект в GM не может просто так бегать, стрелять и плавать: он может унаследовать одно из этих действий, лично имплементировать второе, и всё. Но это только если программист пытается действовать по законам Настоящих Языков Программирования. А с другой стороны, как я уже говорил, объект GM может выполнить событие любого другого объекта того же проекта. Это даже не утиная типизация, это полная анархия, которую может грамотно использовать хорошо программирующий человек. Попытки использовать это кривыми руками, в отличие от традиционных механизмов множественных наследования в традиционных языках программирования (потому что для апофеоза в последних нужно иметь ещё и кривые мозги), заканчиваются довольно печально. Помножьте это на следующую проблему, и будет совсем жутко:
Нет пространств имён
Если вы хотите писать на GML вдвоём (Втроём? Вы в здравом уме???), вам придётся либо называть объекты и их переменные по заранее оговоренным правилам, например дописывать к их названиям приставки, определяющие автора, или какие-нибудь мнемоники их применения; либо просто надеяться, что ваш объект под именем obj_bonus будет единственным во всём исходнике. Более того, из-за глобальной видимости имён совпадать не должно ни одно имя ресурса ни с одной переменной внутри объектов (включая встроенные), и наоборот, иначе всё придётся переименовывать. Разумеется, вручную. К счастью, совпадения имён ресурсов можно проверить автоматически в IDE, однако с переменными такого нет (напомню: «со стороны IDE невозможно предугадать, по какому маршруту выполнения кода пойдёт игра»). Отсюда возникло правило хорошей практики на GML: добавлять к незваниям ресурсов приставки, которые дублируют название их категории. Поэтому спрайт будет называться spr_bonus, объект — obj_bonus, а звук — snd_bonus. Это, кстати, не недостаток языка, а именно недоработка IDE, в которой давно пора бы это автоматизировать.
Шутка:
— Почему профи C++ не любят GML?
— У них не получается написать заголовок цикла.
Пояснение:
В GML нет конструкции for (i=0; i<length; i++), так как нет оператора ++.
Следует писать i+=1.
- 20 декабря 2013, 14:11
- 010
Спасибо за статью.
Только вот, если сравнивать GML с точки зрения именно языка программирования, а не самого подхода ЯП-конструктор, то логичнее было выбрать в качестве противовеса Python/Lua/Ruby, а не плюсы и шарп.
Они не настолько "труёвые", а кроме того я в них не ориентируюсь.
Теперь по тексту. Сразу извиняюсь за занудство.
- нет, он просто является интерпретируемым файлом. Мне не кажется сравнение с динамической библиотекой корректным в любом контексте.
- DLL это просто библиотека, использование библиотек обусловлено возможностью их переиспользования, а также принципом модульности. Но это не значит, что движок рендеринга обязательно выносить в отдельную либу.
- это не проблема GML, это проблема тех, кто на нём пишет... Это вообще немного другой уровень абстракции, писать код так, чтобы к нему ещё и плагины можно было прихерячить, и скрипты.
Я написал "чем-то вроде" только в смысле внешней подключаемости. Хотел расписать подробней, что это вовсе не та DLL, которая DLL, но подумал, что читающие и так поймут, да и текст и так вышел здоровенный и скучный, и без иллюстраций.
Я знаю. Но удобней было бы вещи, не отличающиеся между проектами, выносить. Впрочем, это уже в свою очередь знаешь ты, так что идём дальше.
Так всё правильно, во второй части статьи я писал, чем плох GML, а в первой — просто озвучил потенциал, который можно было бы раскрыть, но этого никто не сделал, либо я этого не застал (но как-то маловероятно, что это так).
- экспериментален разве что в рамках гамака. Event-driven архитектура уже давно и много где используется. Ну или я не понял исходную фразу.
- как что?
- получается, что это не ООП ни разу?
Да, событийная модель это не новость, я имел в виду экспериментальность идеи метапрограммирования объектов через сами объекты.
Ой, тьфу, это я не дописал то, что потом передумал писать (так как ошибся), сейчас сотру.
Нет, почему же, это ООП.
1) Есть наследование;
2) Есть инкапсуляция свойств внутри объектов;
3) Есть полиморфизм, переопределение унаследованного поведения.
События объектов ГМ == методы классов C/C#/Java, с той оговоркой, что у них в ГМ нет параметров (и вызываются они не через обращение к объектам, а из глобального контекста), но так как область видимости переменных спокойно переходит из любого объекта в любой другой, это скорее непривычно и может приводить к трудно выявляемым ошибкам.
- но вызывать их напрямую нельзя, а только через некоторый менеджер. Ядрёная смесь объектно-ориентированного и процедурного подхода =))
Менеджера там отдельно выделенного никакого нет (не, понятно, что он в самом движке есть), ничего создавать-подключать в коде для этого не нужно. Просто пишешь вызов функции, и оно работает.
Тут я приврал, свойств в ГМ нет, но есть переменные. В общем, данные инкапсулировать всё равно можно.
- приведи, пожалуйста, пример.
- а что тогда объект? Это встроенный тип или нет? Если есть встроенные структуры данных, то являются ли они типами данных? Этот момент я не очень понял.
- что, однако, не мешало бы передавать объект не по ссылке, а по значению.
- я правильно понимаю, что массивы это именно массивы, а не list, tuple etc.?
Допустим, мы хотим хранить собственную структуру данных "запись в таблице рекордов". У нас в ней должно быть такое: score — число, time — дата/время, name — строка.
Создаём объект obj_hiscore. В нужном нам объекте, который будет контролировать эти записи, создаём такой код в событии Create (местный аналог конструктора класса):
rectangle=instance_create(0, 0, obj_hiscore)
0, 0 здесь означают, что надо задать новосозданному экземпляру нулевые координаты. Вообще-то нам эти координаты ни к чему, но в ГМ просто нельзя создать объект без задания его координат (и, повторюсь, наследования им целой группы функций для перемещений и столкновений).
Теперь мы можем спокойно описывать внутреннюю логику этого rectangle в его классе, и работать с ним из любого другого места программы, используя точку. Но чем больше мы будем пользоваться такими структурами, тем больше это всё будет тормозить, а в режиме реального времени работать даже с десятком разных структур адекватно вряд ли получится.
Кстати, в ГМ есть ещё два непонятных недо-типа данных — это дата/время и цвет. Оба из них являются просто числами с плавающей точкой, вроде бы двойной точности, из которых встроенными функциями движка можно получать внятные данные о том, что переменные этих типов в себе хранят. А цвет в ГМ это просто упакованные в три байта данные в формате RRRRRRRRGGGGGGGGBBBBBBBB, перепакованные потом в число с плавающей точкой, чтобы не нарушать безобразной, но однообразности GML, где типов данных в итоге два — вещественные числа и строки, а структур по-прежнему нет.
Объект в ГМ это тип ресурса проекта, и более ничего. Каждому объекту сопоставляется его идентификатор, который является обычным числом, начиная с нуля. Когда в коде пишешь with obj_player.x+=10, интерпретатор это понимает просто как with (0) x+=10 (если obj_player был создан нулевым объектом в проекте), находит экземпляр с номером объекта 0 и выполняет ему этот код. Идентификатор экземпляра объекта ГМ это другое, этот номер экземпляр получает при создании, он уникален, и это самое близкое к указателям, что есть в ГМ, то есть всё-таки своеобразные указатели есть, но указывать можно только на объекты. Только вот вспомним ещё раз о том что у скриптов нет именованных параметров, и поймём что тогда придётся писать argument0.name="Vasya", и забьём на это дело.
И эти встроенные типы данных вряд ли можно назвать типами данных в нормальном виде, это просто какие-то оторванные конструкции, с которыми ничего нельзя делать, кроме как то, что предписано функциями, зашитыми в движок, заранее под них написанными.
Мешает то, что в ГМ объект не является типом в традиционном понимании. Или, наверное, вообще не является типом. Передавать можно в итоге только переменные только двух типов — вещественные числа и строки.
Массивы здесь это те простейшие массивы, которые array[i] и array[i,j] без всяких там геттеров-сеттеров (аксессоров) или возможности индексировать их через строки; списки и кортежи тут ни при чём.
Судя по отсутствию ответа, рассказываю я не очень доходчиво, м-да...
Если честно, то система типов мне всё так же непонятна, хотя ты ужа не в первый раз объясняешь -_-
Типов в ГМ только два — вещественные числа и строки, всё.
Объекты в ГМ не являются типами. Они не содержат методов, но содержат обработчики событий, которые с ними происходят.
Вроде бы всё понятно?
Достаточно жуткая картина.
Если ограничение массивов в 32000 ячеек, то как получать доступ до пикселей спрайта? Ведь в спрайте обычно больше точек.
Кстати, двумерными массивами пользовался последний раз года три назад. Не нужны, хватает одномерных.
А вообще таки да, я начинаю сочувствовать авторам на гм.
Доступ до пикселей спрайта? Эк размечтался, такой роскоши ГМ не позволял никогда, и вряд ли будет. Большой досадой для меня было в первое время работы с ГМ, что я не могу сделать характерное олдскульное переключение палитры спрайта, типа эффекта мерцания Марио при неуязвимости, или Мегамена при смене типа оружия. Все спрайты должны быть заранее перекрашены, и выводить на экран можно только их. Естественно, хотелось бы ожидать от ГМ каких-то удобств в виде группировки спрайтов по категориям, но об этом до сих пор никто не задумывался, IDE остаётся практически неизменной с тех пор, только докручивают новые целевые платформы и всякие новомодные штучки да API.
Массивы массивами, а вот без структур как-то грустно бывает.
- ы, то есть нельзя взять белую текстуру и через RGB её раскрасить, как хочется?
Э... Если она вся белая, то по-моему её никак не раскрасишь ни через что, только если нарисовать поверх неё что-то уже. Если ты имеешь в виду почти белую текстуру с пикселами разной интенсивности, которые можно менять через какие-нибудь преобразования, тогда я понял, о чём это, но в ГМ так нельзя. Можно только "блендить" спрайт или текстуру в какой-нибудь цвет, но результат редко бывает хорошим, и раскрашивать отдельные их части нельзя.
- ну как тебе сказать.
Я не понял, в чём соль этих функций, но GM в любом случае не работает с OpenGL, и ничего подобного там нет. Правда, в Студии уже есть шейдеры, но это уже другое.
Устанавливают цвет в OpenGL. Далее этот цвет "смешивается" с цветом рисуемой текстуры, если вкратце. Если текстура белая, а это (r:1 g:1 b:1) каждый пиксель, а glColor у нас выставлен в (r:1 g:0 b:0), то и текстура будет красной рисоваться.
А, ну я так и догадывался. В достудийном GML такого нету, да и после — вряд ли. Всё же, как я уже сказал, есть блендинг, наверное он делает что-то похожее на то что ты сказал.
Собственно, а какая графическая библиотека лежит в основе гамака? Если оно теперь собирается под Андроид и айОс, то там в любом случае под капотом должен быть OpenGL.
До Студии был только DirectX. Но в новых версиях всё равно не появилось таких функций, доступных именно в GML, которые могли бы что-то подобное делать. Возможно, существуют пакеты расширения для этого.
- видимо, можно было бы обойтись сишным механизмом возвращаемого функцией значения, но поскольку функций в гамаке, как таковых, нет, как я понял, то не прокатит. И это какой-то АДЪ.
- множественное наследование это не так критично здесь, а вот отсутствие интерфейсов удивляет. Насколько я понял, сам объект внутри себя вовсю пользуется не наследованием, а агрегацией, но смертному программисту на GML эти радости недоступны.
Можно возвращать значения из скриптов. Можно не возвращать. Можно ловить их. Можно не ловить. Интерпретатор ничего не скажет, IDE тоже.
Ах да, брейкпоинтов в IDE нет. Никаких. Нигде.
Это можно назвать и агрегацией, но, выражаясь точно, это никак не урегулированная возможность вызывать всё отовсюду. Можно вызывать событие таймера босса из объекта выстрела, не связанных ни иерархией наследования, ни пространствами имён (которых нет).
Отсутствуют как таковые.
- но на этом нельзя построить обработку ошибок? Или, правильнее сказать, систему их предотвращения :)
Я тут опрометчиво употребил слово "ловить", это как бы обозначает catch, но я имею в виду именно проверку возвращаемого значения.
Да, можно построить такие системы, но я ведь говорил о том чтобы что-то типа try-catch было ещё на уровне языка. А так-то костылей можно понаставить сколько хочешь поверх этого, но разве это называется "делать игры"? Остаётся просто быть умнее изначально и проверять каждый момент кода.
В Си тоже нет try-catch :)
Ну, бывает. С ним вроде бы удобней, но совсем хорошо было бы программировать игры на таком языке, на котором отлов исключений был бы не нужен. Если вдруг такое вообще возможно.
Такое возможно, но программировать игры (и вообще что-то крупное) на таких языках (см. Coq, Agda, Idris) нереально.
Впрочем, можно минимизировать появление исключений и вообще рантаймовых ошибок, а это опять-таки достигается хорошей статической типизацией.
Ну и два вопроса отдельно от статьи:
1) умеет ли GML в многопоточность, вернее, уметь-то должен, хоть на уровне интерпретатора, но может ли её контролировать разработчик?
2) что с системами контроля версий? Можно ли разрабатывать командой в Гамаке, используя, например, git?
1) Вообще никак, и даже в планах не было.
2) Судя по http://yoyogames.com/studio/collaboration (я глубоко не вникал, и тем более не тестировал), такое есть в ГМ Студии, но это никак не отменяет тех проблем с совпадениями имён, которые описаны в статье, всё равно придётся заранее оговаривать префиксы, либо определять все имена в проекте сразу — и объектов, и переменных.
Телега про игру игр с открытым входом в интерпретатор забавная. :)
У тебя написано Hotline Mayami. Исправь на Miami.
А по теме, пост не очень затронул Настоящие Языки Программирования. Тут в основном, только камни в огород GML.
Да, ошибся в названии, спасибо.
Как же не затронул, когда тут каждый абзац касается его отличия GML от вышеупомянутых? Каким образом они должны были быть затронуты, в таком случае?
Критика в целом справедливая, но в статье каша. Недостатки языка перемешаны с недостатками среды разработки, сравниваются с совершенно разными Настоящими Языками (и с кучей ошибок от их поверхностного знания), причём в том числе с тем, что считается bad practice, язык то недооценённый, то отстой, одни фичи восхваляются и тут же критикуются их последствия. Вообще, нужно определиться, чего мы хотим от встроенного скриптового языка - или хотя бы что стоит взять за образец. А не валить всё в кучу, потому что получится смесь бульдога с носорогом.
Пройдусь немного по пунктам.
> 1. Интерпретатор кода на лету
Здрасте, открыли Америку! Любой уважающий себя динамический язык содержит функцию eval.
> Вы также можете читать и выполнять код из внешних файлов, и даже вовсе не хранить никакого кода внутри проекта не надо возиться со сборками
Для этого в крупных играх прикручивают скриптовые языки типа всяких QuakeC, Lua, и т. д.
> игра, которая читает свой код из внешних файлов, легчайше поддаётся…
…взлому и унижению.
> совместить все существующие… создав некие супер-игры каждого из жанров
…и получится тормозной движок для создания игр в конкретном жанре.
> Почему этого не было?
Потому что для этого ещё нужно всем проектировать архитектуру игры одинаковым образом. А для этого всем нужно одинаково думать. А для этого всем нужно быть клонами, как минимум.
> 2. Механизм добавления событий
Опять-таки, в любом нормальном динамическом ОО-языке можно в любой момент прикрутить произвольному объекту произвольный метод, так что никаких “недооценённых мощностей”.
> и некую сетку, смысла применения которой я так и не понял
Обычный двумерный массив со слайсами.
> Как следствие, такая имитация будет неслыханно тормозить, если этих объектов понадобится много.
Не “будет”, а “может”. Зависит от реализации - в теории оверхед от встроенных событий и переменных можно сделать совсем небольшим, как на практике, не знаю, надо бенчить.
> огромное количество встроенных переменных и функций выглядит так, словно там вместо прочерков должны быть точки
+много :)
> В связи с этим никогда не возникнет проблем с приведениями целого числа к вещественному, либо наоборот.
Отсутствие целых - вообще пушка. Потому что у плавучки совсем другая арифметика, не гарантируются некоторые свойства (коммутативность, например) и в итоге могут вылезти косяки со сравнением, округлением и т. д.
> null отсутствует как таковой, но есть ключевое слово noone, обозначающее "никакой объект"
А в чём отличие второго от первого?
> И нет, нельзя передавать в функцию вычисления расстояния объект точки в пространстве
Потому что там нет “объекта точки в пространстве”?
> это невозможно ещё и потому что в GM нет указателей
Ну их в большинстве высокоуровневых языков нет (или они задвинуты в недра unsafe-подмножеств), ибо нефиг. А идентификатор экземпляра вполне можно считать ссылкой.
> Вложенных или ступенчатых массивов — не существует здесь
Э, а сделать значением массива другой массив нельзя?
> надобность создавать экземпляр объекта, который по сути является статическим и используется как статический — это в терминах ООП некорректно
1) в терминах ООП нет понятия “static”;
2) в C++, например, статическое поле класса тоже нужно инстанцировать отдельно (кроме целочисленных типов, кажется).
> Нет именованных параметров у функций
Это действительно ужасно. Впрочем, не вижу проблем заводить временные переменные со вменяемыми именами - не стоит экономить на спичках, вызов скрипта не самое тормозное место, которое следовало бы оптимизировать.
> Нет множественного наследования
И слава б-гу! Для справки: оно вообще мало где есть, потому что гемору много.
> никаких интерфейсов или утиной типизации
Какие ещё интерфейсы в динамическом языке? Они нужны, чтобы на стадии компиляции гарантировать, что объект поддерживает некоторый контракт и что определённые методы можно безболезненно дёргать. В динамике никаких гарантий дать нельзя. Можно просто попробовать дёрнуть метод/событие - это и будет утиная типизация.
> объект GM может выполнить событие любого другого объекта того же проекта, то есть они как бы все сразу являются предками-потомками всех сразу
Чего?
> Нет пространств имён
Это проблема заодно многих мейнстримных языков, тут на самом деле не так страшно, потому что проекты небольшие. Гораздо большая проблема - нет областей видимости. Или сделали таки?
> Поэтому спрайт будет называться spr_bonus, объект — obj_bonus, а звук — snd_bonus.
Почти венгерская нотация, чо :) Загляни в какую-нибудь игру на Си - будет то же самое.
Итак, почему же всё-таки GML уродлив? Потому что хреновый синтаксис, хреновая типизация, хреновые архитектурные решения, никакая производительность. И если всё это решить, получится совсем другой язык.
чёВ заголовке я так и сказал, что там есть и одни, и другие. Сортировать абзацы по категориям принадлежности этих недостатков я не вижу смысла, да и для конечного пользователя GM в реальной практике это не имеет значения. И в альтернативных IDE для GML я ничего особо нового не нашёл тоже в своё время.
Обратного не утверждал: два пункта в статье повествуют об "использования всей мощности [GML] которого я не видел ещё ни в чьём исполнении".
Прикольно, но я это знал со времён Дюка Ньюкема 3D, в Game.def которого я залезал Блокнотом и заставлял дробовик стрелять мыльными пузырями. А вот делать для игры на ГМ ещё один скриптовый субъязык — ну это уже костыль так костыль.
Со взломом понятно, а как можно унизить игру? По-моему можно только оскорбить автора, если его игру прилюдно "испортить", но и только. Но я такого вандализма в любом случае не видел...
Может быть, может нет. Наши фантазии на эту тему разошлись.
Рамки жанров диктуют архитектуру игры довольно-таки жёстко. Объект выстрела в шмапе не может быть абы каким, и за десятки лет он сильных изменений не претерпел. Он имеет мощность, перемещается в двухмерном пространстве, и наносит повреждения. Аналогично для прочих устоявшихся конструкций, я же не зря обратил внимание именно на пожанровое объединение. С играми смежных и непонятных жанров дело конечно другое.
Недооценённым я назвал именно язык (и я имею в виду то, что читается в дискуссии заядлого (или даже не очень) сишника и ГМщика, невооружённым глазом), а мощности ГМ я назвал неиспользованными в полную силу, не надо путать.
В сейв не сохраняется автоматически (в отличие от массивов и всех встроенных и невстроенных переменных), использовать как-то неудобно. Для меня это значит что оно не нужно, однако вдруг оказалось, что кто-то этим таки пользуется, так что теперь я проведу тесты на эту тему.
Не "может где-то там в теории", а таки "будет на практике", потому что "я говорю об IDE той же версии 8.1", и имею в виду совершенно конкретный экземпляр софта, который давно у меня на руках, и всё что сделают после него — будет уже не релевантно к этой статье, так как я не критиковал саму теорию, а описывал изначально странную попытку сделать структуры в ГМ из объектов, которые для этого не предназначены (ведь содержат кучу встроенной ерунды и по задумке все должны быть игровыми объектами, а не контейнерами данных). Что же до IDE ГМ Студии, точных оценок я ей не дам, да и давать не собирался. А в теории возможно настолько много, что я на неё упор не делаю. Соотношение количества сделанных игр к количеству задуманных у меня примерно 1 к 100, мне уже хватит мечтать.
Они усердно вылезали где-то в хронологическом районе пятой-шестой-седьмой версии ГМ, который сначала обрёл функцию для задания степени точности вычислений, а потом потерял баги связанные с этим, а затем и оную функцию.
noone это, в каком-то смысле, скрытый от юзера ГМ элемент перечисления (enum), являющийся константой-магическим числом, равной -4.
show_message(string(other))
show_message(string(all))
show_message(string(noone))
Его можно сконвертировать в строку и получить "-4". Получать в результате выполнения x/0 noone это как-то странно... Nullable-типов естественно в ГМ тоже нет.
Да, нет встроенного и слишком громоздко городить свои, если точек много.
Ссылка ссылкой, а:
Некрасиво.
В ГМ можно сделать значением переменной только одно вещественное число, либо только одну строку. Всё. Можно хранить в массивах идентификаторы объектов-контейнеров, я даже сам делал такую надстройку. Но потом я понял, что это искусство поедания кактуса мне чуждо.
http://ru.wikipedia.org/wiki/Статический_класс
"Статический класс — сущность в объекто-ориентированном языке программирования."
Что я делаю не так?
Это лишняя работа и читателю кода, и писателю. И вообще как-то не суть геймдева совсем.
Я не знаю, кто и что конкретно подумал, когда прочитал это место статьи, но поясняю: я имел в виду, если бы кроме предка объекту можно было бы указывать ещё какие-то объекты, от которых всё наследуется ещё поверх унаследованного, что в ООП соответствует наследованию одного класса и имплементации остальных интерфейсов — то это было бы удобней, а этого в ГМ нет. А я уже много раз упирался в то, что я хочу чтобы объекты наследовали много чего. Могу попробовать вспомнить.
От утиной типизации возможность выполнять любые события в ГМ отличается тем, что нет никакой проверки на тип и свойства вызываемого и вызывающего, никакого сопоставления.
Ну, тут немного фигни вышло, слишком вольная переформулировка. Пусть всё после запятой в этой цитате станет отныне невидимым!
Не сделали.
Прекрасно знаю о чём речь, навидался, и впредь такое видеть не хочу.
Спасибо за статью. Ты меня окончательно убедил в том, что GM мне не нужен.
А что тогда нужно? На чем писать?
На С++ конечно! Он остаётся самым старейшим и распространённейшим языком для программирования игр. Причём чем серьёзнее игра, тем больше вероятность, что она сделана на плюсах.
Другой вопрос "на чём писать игры, чтобы писать поменьше?".
По-моему, оптимальный вариант - движок на плюсах, игровая логика на скриптовом языке.
Само собой, это традиционная связка.
самая бааааальшая проблема - адовое количество говнокода и быдлокода. в том смысле, что разработчика по ходу вообще не знают слова оптимизация. где-то было мнение, что если бы существовала наормальная культура оптимизации, то игры на том же UE3 весили бы в 5-6 раз меньше (ИМХО; не мое мнение).
с этим вообще проблемы, в целом в индустрии ПО, и это можно было бы списать на заговор по раздуванию ПО, если бы не Бритва Хэнлона
- завис на этой фразе.
Нет никакой проблемы ни в целом, ни в индустрии ПО - там где нужно пролезть в игольное ушко, там по-прежнему всё у всех пролезает. Оптимизация штука хорошая, но трудоёмкая и долгая. Там где она не нужна, ей никто в здравом уме не заморачивается.
Я бы для начала взял Python + PyGame, например. Полноценный язык без описанных гамаковских костылей, лёгок в освоении, прививает хорошие практики в написании кода.
Можно попробовать Unity.
Есть SDL, OpenTK, в конце концов. Пишешь на тех же крестах, потом привязываешь скриптинг на Lua, AngelScript, Python whatever. Это, правда, может быть не так тривиально, как звучит, но и ничего смертельно сложного там нет ;-)
Не советовал бы его, честно говоря. Слишком ограничен в возможностях.
Посоветовал бы Pyglet, у которого реально красивое API и довольно много удобных фич, но, вспоминая его капризы при попытке запуска на разных машинах, начинаю сомневаться, стоит ли советовать такой непредсказуемый инструмент.
- вряд ли более ограниченный, чем Гамак. Или я неправ?
Все удобные функции рисования в Pygame не используют OpenGL для рендеринга графики, а являются оберткой над функциями SDL. То есть, Pygame ограничен возможностями 2D-рисования этого самого SDL. Помнится, мне нужно было элементарно нарисовать полупрозрачный спрайт с полупрозрачными пикселями, и выяснилось, что Pygame этого не умеет (можно либо одно, либо другое).
Хотя я, кстати, давно его не щупал, и там могло многое поменяться.
Смотря что. Всякие серьезные вещи (прям серьезные-серьезные) до сих пор пишутся на C++, потому как если нужна максимальная производительность, лучше плюсов ничего не найдешь. Но и труда в этом случае нужно вложить по максимуму.
Хороший компромисс — Unity. Но для получения максимума возможностей нужно платить деньги.
Для двумерных игр хорошим вариантом считаю Love2D. Крайне простой фреймворк с удобным API и хорошей производительностью. Использует Lua в качестве языка для разработки. В принципе, неплохой выбор для новичка. Но предполагаю, что крупные игры на нем писать будет несколько проблематично.
Ваша неправда. Ассемблер.
Я все-таки с учетом какой-никакой практичности это заявил.
Ну, я же не говорю писать игру под какой-то конкретный микроконтроллер.
Спорненько.
Что именно, ассемблер медленней плюсов?
Точнее, так: средняя программа, написанная на ассемблере, медленнее сопоставимой по функциональности средней программы, написанной на плюсах (и при этом требует намного большего времени и усилий). Потому что компиляторы уже давно производят низкоуровневые оптимизации намного лучше людей.
Спасибо, именно это я и имел ввиду :)
Где-то была статья-опровержение о том, как плохо современные компиляторы справляются с оптимизацией кода. Большой и сложный алгоритм, конечно, неразумно реализовывать на ассемблере и оптимизировать руками, но вот отдельные моменты писать на ассемблере до сих пор имеет смысл.
Про пользу некоторых вставок и интринсиков, конечно, соглашусь. Но пойнт был вот в этом:
(вел. и уж. Луговский)
И тут я резко вспомнил GML. (ну, вообще-то начинали эту дискуссию с него, если кто помнит)
Интересно посмотреть на ассемблер при выводе графики. Будешь проц нагружать всем этим делом что ли?
В смысле? Графику по-любому выводит процессор, GPU или CPU. К GPU можно получить доступ через API. Сам не занимался, но kkrieger как бы выглядит убедительно. Правда, не могу сейчас найти официальной информации, что он действительно на ассемблере был написан, но вроде в своё время говорили так.
Под процессором, понятное дело, понимался ЦПУ. Что за API? Драйверы что ли? CUDA? Так это уже не ассемблер.
Немного прокосячил с терминологией.
http://en.wikipedia.org/wiki/Shading_languagehttp://en.wikipedia.org/wiki/ARB_assembly_language — "ассемблер" GPU.
А API я имел в виду к DirectX/OpenGL.
Нет, это всего лишь языки написания шейдеров. Они достаточно высокого уровня по сравнению с ассемблером.
И даже на том, что ты второе привел, как мне кажется, много не напишешь. Хотя тут я не Копенгаген
Ещё интересно, как на современных осях получить из ассемблера доступ к периферийному оборудованию.
http://en.wikipedia.org/wiki/UEFI , наверное.
Эмм, я всегда думал, что UEFI это замена BIOS, как ты получишь к нему доступ из оси, минуя драйверы материнки?
Я не понял, при чём тут драйверы, вроде в твоём исходном вопросе о них не было.
Но когда я почитал статью, но которую сослался выше, я понял, что это что-то не то. В конечном счёте я не понял твоего вопроса, соответственно не могу на него ответить. Поясни проблематику.
У меня нет вопроса, у меня есть утверждение, что сегодня ты на голом асме игру не напишешь на практике :)
Если под "сегодня" понимать "за одни сутки", то тут уж поспорить сложно, одни сутки это половина Ludum Dare. Что до ассемблера, то когда я проведу соответствующие исследования, я расскажу об этом, если они будут успешны. А пока утверждение принято как гипотеза.
Под сегодня нужно понимать текущий этап развития разработки ПО.
В таком случае, что-то мне подсказывает, что ты имеешь в виду конкретно графические игры. Потому что и Тетрис первый (или по крайней мере один из первых) был в текстовом режиме 80x25, и написать движок текстовых квестов не столь сложно.
Бляяя, Кситилон, не морочь мне голову. Иди кури маны и проводи исследования. Если сможешь целиком на ассемблере игру написать, то тебя в любой айтишной конторе будут ждать с распростёртыми объятиями. Желаю удачи!
Спасибо.
GS: What language was used to write .kkrieger ?
FG: C++, plus some assembly language code where it really matters: mainly texture generation innerloops and the sound synthesizer.
C++ is about the only language fit to our rather special requirements. We have both rather low-level (e.g. memory management, texture generation) and high-level (e.g. scene graph, world management) components, and we need a language that is suitable for both, with adequate compiler support, and at the same time also allows us to write very compact code.
(из интервью с разработчиком)
Об этом я и сказал чуть выше.
Для 2д графона, кстати, современные cpu его выводят влёгкую. Загружая при этом его на 5-20 процентов всего даже в больших разрешениях.
Я одно время писал на асме, так что чуть чуть в теме.
Смотри, как получается: на языке высокого уровня ты напишешь алгоритм за час, и потом двое суток сможешь его отлаживать и переделывать.
На асме ты за двое суток этот алгоритм едва-едва напишешь. Сроки больше, ведь менее удобно.
Поэтому, реальная скорость вполне может оказаться выше на языке высокого уровня: ведь тебе было удобно переделывать. За тестовый срок ты мог перепробовать несколько алгоритмов и выбрать лучший, мог отшлифовать свой алгоритм.
На асме же глубоко и много переделывать алгоритм неудобно, долго. Поэтому, он может оказаться медленнее.
К тому же, не забывай: 90% влияния на скорость даёт _выбор_ алгоритма, и только 10% даёт его _реализация_. Поэтому яп высокого уровня, где удобно повыбирать алгоритм, обычно лучше.
Но зачем кодить алгоритмы, если можно сначала всё посмотреть в виде формул, сделать чисто математическую оценку сложности алгоритмов?
Твоё время решает. Кодить и отлаживать эффективные алгоритмы дольше и сложнее.
Пример: за 2 часа работ ты можешь на удобном языке высокого уровня закодить эффективный алгоритм. На ассемблере за этот же срок ты успеешь закодить лишь решение "в лоб", которое не будет эффективным. Итог: на асме в несколько раз медленнее работает, а время потрачено равное.
Скажи, а разве так часто пишут новые алгоритмы? Не включая сюда новомодные технологии и 3D, так как я бы ими всё равно едва ли пользовался.
В идеальном мире, наверное, так и происходит. После этого набирается код, разом компилируется и незамедлительно распространяется среди игроков. :]
Точно. Правда, по нынешним стандартам C++ местами безнадёжно низкоуровневый.