Меню

Сергей Драган

Разработка игр и разные мысли

Как я HTML5-игру с OpenFL делал (и наплодил больше багов, чем там было самой игры)

В этом посте я собрал список проблем, с которыми столкнулся при разработке простой HTML5-игры на связке Haxe-OpenFL-Box2D. И самому себе на будущее, и кому-нибудь, может, тоже пригодится.

В первую очередь дисклеймер: мои руки растут из жопы, я плохо разбираюсь в OpenFL и Haxe в целом. Я понимаю, что это опенсорс и «если что-то не устраивает — возьми и почини сам». Также я очень благодарен всему русскому сообществу Haxe за неоднократные консультации и помощь!

На киевском DevGamm 2013 я хотел штурмовать Speed Game Dating, и для этого на пару с художником мы сделали за две недели Cake Break — Box2D-физпаззл на флеше. Времени было немного, поучаствовать хотелось, а игры такого плана делаются как раз быстро.

Спонсоры, глядя на игру, с равнодушным лицом отвечали: «Meh», добавляя, что вот если бы она была на модном HTML5 — то было бы, конечно, совсем другое дело, и что как только её портирую — сразу идти к ним.

Не вопрос! Откопал Haxe, сделал «haxelib install box2d», восхитился: «Как же легко портировать с флешика на хакс! Вот буквально только int на Int заменить и void на Void!», а дальше маленько обосрался, наивно полагая, что раз работает на десктопе и айпаде, то и везде будет работать. Как же я был наивен.

bad-computer

(далее…)

Год в Германии — впечатления

Чуть меньше года назад сбылась моя розовая мечта, и я переехал в Германию. Началось с того, что со мной через LinkedIn связался рекрутер из гамбуржской игродельной конторы, письмо которого я сперва проигнорировал, потому как «ну кого может заинтересовать какой-то зачуханный быдлокодер-говноляп аж из Украины?». Пробы ради ответил и — опа! — прошёл собеседование.

Переезд

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

Наибольшие задержки были, во-первых, из-за того, что контракт по бумажной почте всё никак не доходил, и, во-вторых, из-за диплома: посольство не хотело выдавать визу. В Германии есть специальная БД зарубежных ВУЗов и специальностей, выпускники которых могут получить Blue Card (визу для высококвалифицированных работников, навроде врачей, программистов и инженеров) — http://anabin.kmk.org/no_cache/filter/institutionen.html (там вкладка «Suchen»). Хоть в ней и есть мой ВУЗ, моей специальности там не было, потому работодателю пришлось подтверждать мой диплом через немецкое бюро трудоустройства (чтобы последние посмотрели на мой диплом и выдали официальную справку, что, мол, «выпускники этой специальности — норм ребята, в компах шарят, к высококвалифицированным работникам относятся»), и потом мне отправили соответствующее письмо в Украину, с которым я уже в посольство и шёл.

Кстати, без диплома Blue Card не выдают, и переезд был бы невозможен. С благодарностью вспоминал родительское: «Иди учись и не выдумывай глупостей! Ишь ты, университет бросит он, в жизни оно ему не пригодится!».

Диплом котируется

В конце-концов все необходимые бумаги были собраны (к слову, не так их и много нужно для национальной визы — даже меньше, чем для туристической), и спустя две-три недели мне позвонили из посольства, пригласив зайти с загранпаспортом и получить визу. Так что в мае 2014 я собрал дорожную сумку, закинул на плечи рюкзак, и, тихо пища от восторга — настолько круто, оказывается, летать в самолёте — полетел из Киева в Гамбург. (далее…)

OpenFL: Float32Array is not defined

Я знаком с OpenFL крайне поверхностно, потому каждый раз, когда он ведёт себя «как-то не так, как вёл бы Flash» — искренне пугаюсь и теряюсь.

Всё было хорошо, пока не обнаружилось, что HTML5-игра не запускалась в IE9, говоря: «Float32Array is not defined».

Наверняка, есть изящные и удобные способы решить эту проблему, но мне помогло открыть сгенерированный .js, найти в нём эту строку

this.__array = new Float32Array([a,b,c,d,tx,ty,0,0,1]);

и заменить на эту:

this.__array = [a,b,c,d,tx,ty,0,0,1];

Уверен, что это не лучшее решение, и скорее всего при этом от игры что-то втихаря отваливается — но у меня всё вроде бы заработало.

cat_smiling

Уроки, вынесенные из не слишком удачной социалки

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

Команда подобралась настолько компактной, насколько возможно: серверщик, клиентщик, дизайнер, художник-аниматор. Раздувать штат инвестор не хотел, потому как сам не был уверен в целесообразности данного предприятия. При этом каждый (кроме, пожалуй, меня) хорошо знал своё дело, и был в нём действительно хорош. Казалось бы, всё должно получиться!

Ошеломляющего успеха не было, retention и платежные показатели оказались крайне скромными (правда, и деньги в раскрутку не вкладывались — оценки делали по первым 200к игроков, пришедших в игру в первые недели просто из каталога Вконтакте, пока игра висела в разделе «новые»). В вопросах монетизации и геймдизайна я много опыта всё равно не набрал, однако несколько выводов для себя сделал, и хотел бы ими поделиться — может, кому пригодится. Тем более, сейчас, работая в большой игровой компании, я особенно чётко вижу, как умные и опытные люди избегают моих «детских» ошибок.

На эту же тему, к слову, я ещё могу порекомендовать хорошие статьи «как умудриться совершить 14 ошибок, разработав одну социальную игру» и «Целенаправленный сбор и анализ граблей в разработке игр для соцсетей«.

Нижеизложенное, повторюсь — это сугубо мои персональные выводы (во многом капитанские), основанные на собственных наблюдениях и (часто) ошибках. Итак!..

Я понятия не имею как проектировать

Не переоценивать себя

Приступая к проекту, я не имел раньше опыта в разработке игр такого размера и масштаба, и легкомысленно говорил себе: «Ну, сделать игру, в которой в пять раз больше фич и контента, чем в играх, которые я делал до этого, займёт в пять раз больше времени», — и сильно заблуждался. В результате вылезло великое множество нюансов и подводных камней, о которых я даже не догадывался, и, по большому счету, знать не мог. «Нам не нужен геймдизайнер, почитаем статей, посмотрим на похожие игры — и сами со всем разберемся», «альфа-версия будет готова к сентябрю, потому что мне кажется, что этого будет вполне достаточно», «у других компаний сроки большие из-за бюрократии, раздутого штата и слишком формализованных процессов, а мы сейчас как настоящие инди набросимся и все сделаем».

Возможный выход: попросить подробную консультацию тех людей, которые уже таким занимались и съели собаку. Даже если за это придётся заплатить денег — оно себя окупит. (далее…)

Итоги 2014

Год закончился, почему бы и не подвести итоги?

Побывав на киевском девгамме я тоже воспылал энтузиазмом, и, наслушавшись, что флеш как обычно мертв, решил, что я теперь у мамы html5-разработчик. Прикинул, что куплю на заработанные за год миллионы, потёр в предвкушении ладошки, взял Haxe+OpenFL+Bitfive и стал делать всякие поделки.

ВНЕЗАПНО спойлер: миллионов нет, за год почти ничего не сделано 🙂
(далее…)

Метод утенка

Недавно прочел о дебаге «методом утёнка» (http://en.wikipedia.org/wiki/Rubber_duck_debugging).

Суть проста: столкнувшись с проблемой в коде, которую не удается понять, ставишь перед собой резинового утёнка (или что угодно другое), и объясняешь ему свою проблему так, как будто рассказываешь о ней живому человеку, потому что правильно сформулированный вопрос часто становится ключом к решению.

Стив Макконнелл в главе «Совместное конструирование» книги «Совершенный код» тоже описывает это:

Вероятно, вам знакома одна довольно распространенная ситуация. Вы подходите к столу другого программиста и говорите: «Не мог бы ты взглянуть на этот код? Он не работает». Вы начинаете объяснять: «Причиной не может быть вот это, потому что я сделал то-то и то-то. Причиной также не может быть это, потому что я сделал вот это. Кроме того, причиной не может быть… подожди… Это может быть причиной. Спасибо!» Вы решили проблему, хотя ваш «помощник» не произнес ни слова.

Дергать других я не люблю, потому объяснил проблему первому, что нашлось под рукой — пластиковой бутылке. И что я скажу: это работает! Бутылка не только внимательно и не перебивая выслушала меня, но и быстро помогла найти причину — буквально за двадцать минут после двух часов безуспешного чесания затылка и тяжких вздохов.

Одним словом — ещё одна замечательная вещь из разряда: «Это же так просто, почему я не делал это раньше?!».

quack_quack_mtfckr

Как остановить setTimeout()

Вообще, использование setTimeout() не слишком желательно, потому как странно ведёт себя с памятью, но в некритичных для производительности местах — вполне можно.

А для остановки setTimeout() (т.е. когда после его вызова необходимо остановить таймер, чтобы указанный метод не выполнился по таймауту) используется метод clearTimeout() — вот так:

var timeoutId: uint = setTimeout(myMethod, 1000);
clearTimeout(timeoutId);

А чтобы пост был интереснее, вот вам фото котейки в шапочке.

Cat in hat

Observer — паттерн, который изменит вашу жизнь

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

Кстати, слышал любопытное мнение: «Если у тебя есть класс, в названии которого есть слово Manager — значит, у тебя, скорее всего, проблемы с архитектурой приложения». Как думаете? Согласны или нет?

Вот как можно подступиться к этой проблеме:

  • сделать его синглтоном, и обращаться к нему из нужных мест в программе;
  • передавать ссылку на него каждому заинтересованному классу;
  • наоборот, передавать ему ссылку на каждый класс, в котором AchievementsManager заинтересован;
  • использовать Dependency Injection;
  • использовать паттерн Observer.

Вариант с синглтоном не очень ужасен, но что, когда нам понадобиться сделать ещё несколько подобных по принципу действия механизмов? Плодить ещё синглтоны? Так себе решение. Передавать ссылки друг на друга между объектами — выглядит довольно криво, и быстро превратит код в гору мусора. Dependency Injection — красиво и изящно, но несколько сложно в реализации.

И вот здесь нам приходит на помощь паттерн Observer («Наблюдатель»). Я расскажу о несколько упрощённой его вариации, немного похожей на то, как это устроено во фреймворке PureMVC и использующей стандартный EventDispatcher флэша.

(о том, как делать «по-правильному», я рекомендую прочесть в книге William Sanders, Chandima Cumaranatunge — ActionScript 3.0 Design Patterns или — если знаете украинский язык — в замечательной «Дизайн-патерни — просто, як двері» за авторством Андрея Будая)

Итак, в первую очередь создаём некий синглтон (да, увы; но только один), который будет у нас центральным эвентдиспетчером.

import flash.events.EventDispatcher;
 
	public class CentralEventDispatcher extends EventDispatcher
	{
		private static var _instance: Observable;
 
		public function CentralEventDispatcher() 
		{
 
		}
 
		public function broadcastMessage(name: String, content: Object = null):void
		{
			dispatchEvent(new GameEvent(name, content));
		}
 
		static public function get instance():CentralEventDispatcher 
		{
			if (!_instance) _instance = new CentralEventDispatcher();
			return _instance;
		}
	}

Затем делаем NotificationEvent. Это — обычный Event, в котором есть два поля: String name и Object body. Первое используем для того, чтобы получатели событий понимали, от кого и о чём оно; во втором (опционально) находится полезная нагрузка.

import flash.events.Event;
 
	public class NotificationEvent extends Event 
	{
		public var name: String;
		public var body: Object;
 
		public function NotificationEvent(name:String, body: Object = null, bubbles:Boolean=false, cancelable:Boolean=false) 
		{ 
			super(name, bubbles, cancelable);
 
			this.name = name;
			this.body = body;
		} 
 
		public override function clone():Event 
		{ 
			return new NotificationEvent(name, body, bubbles, cancelable);
		} 
 
		public override function toString():String 
		{ 
			return formatToString("NotificationEvent", "name", "body", "bubbles", "cancelable", "eventPhase"); 
		}
 
	}

Эти два класса — всё, что необходимо для того, чтобы организовать Observer. Теперь разберёмся, как этим добром пользоваться.

Когда какому-то классу нужно поставить другие о чём-то в известность, мы делаем так:

public class Game 
	{
		public static const NAME: String = "Game";
		public static const SOMETHING_INTERESTING_HAPPENED: String = NAME + "SomethingInterestingHappened"
		public static const ANOTHER_THING_HAPPENED: String = NAME + "AnotherThingHappened"

		public function Game() 
		{
			CentralEventDispatcher.instance.broadcastMessage(SOMETHING_INTERESTING_HAPPENED);
			CentralEventDispatcher.instance.broadcastMessage(ANOTHER_THING_HAPPENED, {payload:String("Some random stuff")});
		}

	}

А в других классах, которые должны узнавать об этих событиях, просто подписываемся на них при помощи addEventListener. Только нюанс в том, что отсылает их не экземпляр класса Game, в котором событие происходит, а CentralEventDispatcher, так что именно на него нам и нужно подписаться.

public class AchievementsManager 
	{
 
		public function AchievementsManager() 
		{
			CentralEventDispatcher.instance.addEventListener(Game.SOMETHING_INTERESTING_HAPPENED, onNotification);
			CentralEventDispatcher.instance.addEventListener(Game.ANOTHER_THING_HAPPENED, onNotification);
		}
 
		private function onNotification(e:NotificationEvent):void 
		{
			switch (e.name) 
			{
				case Game.SOMETHING_INTERESTING_HAPPENED:
					trace("I am AchievementsManager and I received SOMETHING_INTERESTING_HAPPENED notification");
				break;
 
				case Game.ANOTHER_THING_HAPPENED:
					trace("I am AchievementsManager and I received ANOTHER_THING_HAPPENED notification", {payload:String("Some random stuff")});
					trace("And here is its body contents: " + e.body['payload']);
				break;
			}
		}
 
	}

Вот так. Таким образом, мы можем добавить сколько угодно классов, которые могут слушать любые событие, происходящие в любых концах приложения, при этом ничего не зная об объектах, эти события порождающих — они должны знать только о CentralEventDispatcher.

Очевидный недостаток этого подхода, помимо необходимости держать синглтон — это постоянная генерация Event’ов, что не обрадует GC.

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

О FlashPress.ru и скором релизе

Если вы не слышали о блоге http://flashpress.ru, то рекомендую сразу подписаться — ну или хотя бы просто заглянуть-почитать. К сегодняшнему дню там более сотни действительно полезных новостных и учебных статей о флеше, даже специально разделенных по уровням сложности. Всё написано с любовью, подробно и человеческим языком. Одним словом, пацаны вообще ребята.

(кстати, ещё они есть вконтакте — https://vk.com/flashpress — и твиттере — https://twitter.com/flashpressblog)

К другим новостям: продакт-плейсмент игруля про дочки-матери, которую я делаю на своей фулл-тайм работе, вот-вот релизится. Оглядываясь назад, снова убеждаюсь, что любому более-менее крупному проекту нужно чёткое и подробное планирование с самого начала, и приступать к работе без него нельзя — иначе, скорее всего, масса времени будет, во-первых, потрачена на топтание на месте и перепиливание раз за разом одного и того же, а, во-вторых, вместо того, чтобы сделать действительно качественно небольшой функционал, обязательно возникнет желание сходу соорудить «ММОРПГ в постапокалиптической вселенной с открытым игровым миром как Сталкере, только лучше, а ещё чтобы в космос летать и режим стратегии был. И ещё чтобы пони стреляли лазерами из глаз.».

Я родился!

О невероятной скорости publish во Flash Pro CC

Привет!

Поставил себе на днях новый Flash Pro CC. Вообще, я относительно мало работаю во Flash Pro — код пишу во FlashDevelop, а во FlashPro только компоную и паблишу графику — потому авторитетно оценить новые возможности не могу, но очень хочу поделиться одним впечатлением.

Есть у меня один большой fla-файл с кучей всякого-разного. На моей домашней машине Flash CS6 делает с ним Publish to SWC за 140-145 секунд. Flash CC спаблишил этот же проект за 5 секунд. Пять секунд против двух с половиной минут!

Для чистоты эксперимента я провёл сравнение на разных файлах и даже с CS5.5, и каждый раз CC безоговорочно во много раз уделывал по скорости CS6/CS5.5. Потрясающе. То ли в новом флэше этот модуль писали гении оптимизации, то ли в старом — криворукие гастарбайтеры.

Ну а остальных впечатлений немного. Интерфейс стал симпатичнее, но мне пока непривычно ориентироваться среди новых монохромных иконок в Library, потому как привык, что синее — это мувиклип, зелёное — спрайт, глаз сразу «цепляется» за нужные места. Рисовать мне не приходится (да и делаю я это аки восьмилетний ДЦПшник), потому без понятия, стал ли этот процесс приятнее. Как повисал при попытке копипаста между проектами символов с зависимостями друг от друга, так и повисает.

А, ну и да, не знаю, что они там улучшили во встроенном во Flash Pro редакторе кода (который, как известно, является смесью notepad.exe и сельского сортира), но по-моему, он стал ещё более деревянным и ущербным.

Хорошего вам настроения, друзья!