понедельник, 27 августа 2012 г.

Социальная Flash-игра — Игровое поле


Этот этап занял некоторое время. Хорошо что нашёлся ещё один пример с открытым кодом от Adam Zwakk, что мне очень помогло:

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

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

Вот код примера: скачать. А дальше я расскажу об основных препятствиях.


Система координат


Первым делом необходимо понять систему координат. Поначалу не разобравшись как следует сделал лишнюю работу, прежде чем понял что к чему.

Изображение в As3Iso.lib строится следующим образом:
  • IsoView - {x = 0; y = 0} в центре экрана, положительные значения вверх и влево.
  • IsoScene – координат не содержит
  • IsoSprite - имеет доступ к двум системам координат:
    • 1. IsoSprite.container – (спрайт отображающий объекты IsoSprite) {x = 0; y = 0} в центре экрана, но положительные значения вправо и вниз.
    • 2. Изометрические координаты IsoSprite, о которых сейчас речь не идёт.
  • Sprite – {x = 0; y = 0} в центре экрана, положительные значения вправо и вниз.

Масштабирование и скроллинг


Масштабирование и скроллинг по отдельности устроены довольно просто. Но совмещение этих двух вещей с наложением ограничений на скроллинг может стать проблемой.

Чтобы ограничить скроллинг создаётся специальный спрайт, размером превышающий окно игры и прикреплённый к текущему IsoView следующим образом:

pan_limit_sp = new Sprite();
// задаётся размер и положение в пространстве
updatePanLimitSize(); 
view.backgroundContainer.addChild(pan_limit_sp);
 
view.rangeOfMotionTarget = pan_limit_sp;
view.limitRangeOfMotion  = true;

После этого скроллинг возможен до тех пор пока окно игры расположено в пределах «pan_limit_sprite».

При масштабировании «pan_limit_sprite» должен соотвественно менять размер и положение чтобы границы окна игры постоянно находились внутри него, иначе эта система не будет работать.

Полное решение можно посмотреть в коде примера (ссылка на скачивание выше). Масштабирование выполняется колесом мышки. Попробуйте увеличить спрайт до максимума, передвинуть до упора в любую из сторон, затем уменьшить до минимума.

понедельник, 20 августа 2012 г.

PHP - закругление углов у картинки

Давно было интересно как можно в PHP сделать с картинкой что-нибудь такое как указано в заголовке, а тут ещё и задание соответствующее попалось.

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

Если вам, также как и мне, интересно что значит «GD», то вот ответ:

В GD 1.0, это означало «gif draw». После того как Unisys запатентовало алгоритм LZW сжатия, используемый в GIF и поддержка GIF была убрана, аббревиатура оффицально ничего не обознает, поэтому пусть просто будет «graphics draw» и всё. (поддержка GIF возвращена, благодаря тому что срок действия патента истёк, но GD может гораздо больше чем создание GIF)

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

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

Поэтому последовательность действий у меня следующая:
  1. на временной картинке рисую те части, которые должны стать прозрачными в результате;
  1. накладываю на исходное изображение;
  2. по нужным координатам заполняю прозрачным цветом (результат вверху).
Чтобы прозрачность работала, у исходной картинки необходимо установить соответствующий флаг при помощи функции:
imagesavealpha($img_res, true);
, а вот наличие альфа канала у временного изображения не важно (я использовал true color, потому что в этом случае на один вызов функции imagecolortransparent() меньше).

Решение:
class ImgUtils
{
    public static function imageToRes($img_file)
    {
        $img_res = false;
 
        list($tmp, $tmp, $type) = getimagesize($img_file);
 
        switch ($type)
        {
            case 2: // JPEG
 
                $img_res = imagecreatefromjpeg($img_file);
                break;
 
            case 1: // GIF
 
                $img_res = imagecreatefromgif($img_file);
                break;
 
            case 3: // PNG
 
                $img_res = imagecreatefrompng($img_file);
                break;
        }
 
        return $img_res;
    }
 
 
    public static function roundedCorners($img_res, $radius)
    {
        $width  = imagesx($img_res);
        $height = imagesy($img_res);
 
        imagesavealpha($img_res, true);
 
        // corners START
        $img_tmp = imagecreate($width, $height);
 
        $black  = imagecolorallocate($img_tmp, 0, 0, 0);
        $transp = imagecolorallocatealpha($img_tmp,
                                            0, 0, 0, 127);
 
        imagefill($img_tmp, $width / 2, $height / 2, $transp);
 
        // left upper
        imagearc($img_tmp,  $radius / 2 - 1,
                            $radius / 2 - 1,
                            $radius,
                            $radius, 180, 270, $black);
        imagefill($img_tmp, 0, 0, $black);
        // right upper
        imagearc($img_tmp,  $width - $radius / 2,
                            $radius / 2 - 1,
                            $radius,
                            $radius, 270, 0, $black);
        imagefill($img_tmp, $width - 1, 0, $black);
        // right lower
        imagearc($img_tmp,  $width - $radius / 2,
                            $height - $radius / 2,
                            $radius,
                            $radius, 0, 90, $black);
        imagefill($img_tmp, $width - 1, $height - 1, $black);
        // left lower
        imagearc($img_tmp,  $radius / 2 - 1,
                            $height - $radius / 2,
                            $radius,
                            $radius, 90, 180, $black);
        imagefill($img_tmp, 0, $height - 1, $black);
 
        // corners END
 
        imagecopyresampled($img_res, $img_tmp,
                            0, 0, 0, 0,
                            $width, $height, $width, $height);
 
        $transp = imagecolorallocatealpha($img_res,
                                            0, 0, 0, 127);
        imagefill($img_res, 0, 0, $transp);
        imagefill($img_res, $width - 1, 0, $transp);
        imagefill($img_res, $width - 1, $height - 1, $transp);
        imagefill($img_res, 0, $height - 1, $transp);
 
        imagedestroy($img_tmp);
 
        return $img_res;
    }
}
Изначально я пытался нарисовать «уголки» на временном изображении при помощи одного прямоугольника и одного эллипса наложенного поверх. Оказалось что в таких условиях трудно контролировать радиусь углов — тогда я стал рисовать на каждый угол по своей окружности.

Фотографию предоставила Анастасия Мельникова (StacyLV).

вторник, 14 августа 2012 г.

FlashDevelop - опыт использования

Изучая примеры Flash-игр я начал пользоваться FlashDevelop просто потому, что примеры, которыми я распологал, были созданы в нём. Пользуюсь я им уже около года, без всякого предварительного обучения - всё что мне нужно было для работы с AS 3.0-проектами я смог найти методом тыка (тем более, что документация не поражает глубиной описания).

Сейчас я решил подробнее разобраться в возможностях этой программы и написать о своём опыте. Расскажу о том что было полезно или интересно на мой взгляд.

FlashDevelop (FD) – это IDE (Integrated Development Environment) для вэб-разработчиков, с поддержкой ActionScript (AS) 2 и 3 (и не только AS), с автодополнением, компиляцией и отладкой, шаблонами проектов и много чем другим.

Речь пойдёт о FlashDevelop версии 4.0.4 RTM (RTM? Wikipedia подсказывает, что возможно это "release to manufacturing" или "release to marketing").

Общие заметки

После установки FD полностью готов к работе: можно создать AS-проект, скомпилировать и запустить.

По умолчанию для компиляции AS FD использует бесплатную Flex SDK, которая устанавливается вместе с IDE. Это тот вариант, которым я всё время пользовался.

Основные комбинации клавишь, которые я использовал в работе с FD:
  1. F5 – компиляции;
  2. F8 – тестовый запуск;
  3. Shift+F5 - принудительная остановка отладчика;
  4. и, наверное, ещё ESC - закрытие подсказки автодополнения.
На данный момент моей основной задачей с AS было разбираться в устройстве чужих проектов. Поэтому наиболее востребованной функциональностью являлись:
  1. Всплывающие подсказки при наведение на название класса;
  2. Возможность кликнуть правой кнопкой на названии класса и перейти к его определению;
  3. Закладка «Outline» содержащая интерактивное дерево текущего класса + импортируемые и родительский классы.

Автодополнение работает хорошо и почти не мешается. Особенно нравится автдополнение в директиве import: можно написать только название самого класса, а FD уже выдаст список с полными путями.

Особенности FlashDevelop

По-умолчанию все файлы (*.as и .as3proj) открываются в одном и том же окне программы. Если нужно запускать несколько окон FD, то в корневой папке программы нужно создать файл .multi и перезапустить программу. После этого каждый файл, ассоциированный с FD, запускает свою отдельную версию - это учит пользоваться встроенным Project explorer (можно ещё использовать drag`n`drop на нужном окне).

Также при закрытии FD не запоминает проекты и вкладки всех окон, а только последнего.

При отладке тоже есть свой нюанс (но это скорее относится к самому Flash Player, чем к FD). Если при тестовом запуске приложение не может запуститься из-за ошибки, окно Flash Player  вашего приложения остаётся позади всех остальных окон и не появляется в task bar даже после принудительной остановки отладчика (через меню Debug или Shift+F5). Если это окно не найти и не закрыть, то последующие попытки запустить компиляцию приведут к ошибке:

The process cannot access the file 'D:\Dev\Flash\MyProject\bin\ MyProject .swf' because it is being used by another process.

По не знанию может быть трудно догадаться о причине этого сообщения.

Другая функциональность

FD предлагает большое количество настроек:
  1. Расположения окон интерфейса;
  2. Изменения всевозможных цветов (подсветки синтаксиса и фона окон);
  3. Автодополнения и шаблонов классов;
  4. Изменения пунктов главного меню через XML-файлы;
  5. Некоторых комбинаций клавишь.
Также есть поддержка макросов — внутренних команд («запустить программу», «открыть файл», «сообщение плагину» и др.) с набором аргументов («имя файла/программы» и др.).

Помимо этого, существуют ещё и плагины - скрипты использующие синтаксис C#.

У меня потребность в плагинах не возникала, за исключением одного: Apache Ant. Этот плагин выглядит как дополнительная панель и предоставляет графический интерфейс к одноимённой программе.

Дополнительно из интересного:
  1. Встроенный профайлер;
  2. Закладки (Bookmarks) - напоминают точки останова в отладчике, с возможностью просмотра списка закладок и перехода по нему (причём для точек останова, есть похожая панель, только кликать там надо на колонку с номером строки);
  3. Список задач (Task List) — по исходному коду ведётся отслеживание меток (по умолчанию это - TODO, FIXME, BUG). В панели Tasks выводится список найденных меток с указанием файли и строки;
  4. Автоматическое построение документации (интерфейс к ASDoc);
  5. Анализ кода на потенциальные ошибки;
  6. Проверка синтаксиса. Запускается автоматически, когда начинаешь редактировать код, из-за этого FD может «задумываться» на несколько секунд, но это случается изредка;
  7. Просмотр типов проекта (Type Explorer) – дерево всех классов с их открытыми методами. Практически тоже самое, что и закладка Outline только глобально.
Заключение

Изначально я не выбирал какой редактор использовать, а просто взял что попалось первым. Сейчас после года использования, привыкнув к немногим недостаткам, FD мне кажется очень удобным.

Единственная бесплатная альтернатива FD, которую мне удалось найти, и которая не заброшена разработчиками это FDT 5 Free. Нужно попробовать её в работе, тогда будет что сравнить с FD.

понедельник, 6 августа 2012 г.

PHP Sockets - отправка файла

Это продолжение записки "PHP модуль Sockets для HTTP запросов", здесь будет дополнение к ранее написанному и я не буду приводить весь код класса.

Уже при написании HttpSocket мне было интересно узнать, как составить HTTP заголовок для отправки файла. Поэтому я решил разобраться что и как, и дописать эту функциональность.

Как указано в RFC 1867, при отправке файла методом POST, заголовок Content-Type должен принять значение «multipart/form-data». Также Content-Type должен содержать определение boundary:

Content-Type: multipart/form-data; boundary=ABCuniq_idABC

Более того теперь тело запроса разделено на несколько частей начинающихся, разделённых и заканчивающихся строкой указанной в boundary. Каждая часть содержит свой «подзаголовок» и выглядит следующим образом:

--ABCuniq_idABC
Content-Disposition: form-data; name="var_name1"

value1
--ABCuniq_idABC
Content-Disposition: form-data; name="var_name2"

value2
--ABCuniq_idABC
Content-Disposition: form-data; name="img_file"; filename="IMG_20120305_102159.jpg"
Content-Type: image/jpeg

<содержимое файла>
--ABCuniq_idABC--

Обратите внимание, как к строке boundary добавляются символы «--».
Указанная информация о файле, в случае удачной отправки, на принимающей стороне может выглядеть примерно так:

$_FILES = Array(
[img_file] => Array(
[name] => IMG_20120305_102159.jpg
[type] => image/jpeg
[tmp_name] => C:\WINDOWS\Temp\php5DF.tmp
[error] => 0
[size] => 450055
)
)

В дополнение к сделанному, я думал что лучше будет отправлять файл закодированным в base64, чтобы данные не исказились, да и при выводе заголовка он выглядит лучше. Я исследовал такие поля как Content-Encoding, Transfer-Encoding и даже Content-Transfer-Encoding.

Как выяснилось, протокол HTTP принимает двоичные данные (содержимое файла) без проблем и кодирование не нужно. Поля Content-Encoding, Transfer-Encoding нужны соотвественно для сжатия и передачи содержимого по частям, а поле Content-Transfer-Encoding не поддерживается HTTP и используется в почтовых протоколах.

Если с заголовком всё понятно, то написание метода не создаст проблем. Но перед этим, одно «но»: на данный момент в PHP нет надёжного встроенного способа определить MIME-тип произвольного файла. Зато есть простой способ определить MIME-тип изображений. Поэтому я буду отправлять только изображения.

Получившийся у меня код метода класса HttpSocket:

public function reqPostFile($addr,
                            $var_arr = array(),
                            $file_path = null)
{
    $this->parseAddr($addr);
 
    $boundary = '>>============' . uniqid() . '<<';
    $req_body = '';
 
    foreach ($var_arr as $var_name => $var_val)
    {
        $req_body .= '--' . $boundary . NL;
        $req_body .= 'Content-Disposition: form-data;' .
            ' name="' . $var_name . '"' . DNL;
 
        $req_body .= $var_val . NL;
    }
 
    if (!is_null($file_path))
    {
        if (($mime = getimagesize($file_path)) !== false)
        {
            $mime = $mime['mime'];
 
            $req_body .= '--' . $boundary . NL;
            $req_body .= 'Content-Disposition: form-data;' .
                ' name="img_file";' .
                ' filename="' . basename($file_path) . '"' . NL;
 
            $req_body .= 'Content-Type: ' . $mime . DNL;
            $req_body .= file_get_contents($file_path) . NL;
 
        } else {
 
            trigger_error('Mime type of image 
                "' . $file_path . '" can`t be detected',
                E_USER_ERROR);
        }
    }
 
    $req_body .= '--' . $boundary . '--' . NL;
 
 
    $header = 'POST ' . $this->query_str . ' HTTP/1.1' . NL;
    $header .= 'Host: ' . $this->host_str . NL;
    $header .= 'Content-Type: multipart/form-data;' .
        ' boundary=' . $boundary . NL;
 
    $header .= 'Content-Length: ' . strlen($req_body) . NL;
    $header .= 'Connection: Close' . DNL;
 
    $header .= $req_body;
 
    return $this->doQuery($header);
}