Главная » Программирование игр под Android » ANDROIDGRAPHICS И ANDROIDPIXMAP: ДВОЙНАЯ РАДУГА

0

 

Итак, возвращаемся к нашей самой любимой теме: программированию графики.

Однако есть еще один аспект, изучение которого мы отложили до текущего момента, а именно: что делать с экранами различного размера и разрешения.

Обработка различных размеров экрана и разрешений

Android поддерживает различные разрешения экрана начиная с версии 1.6. Она может обрабатывать разрешения от 240 х 320 до 480 х 854 пиксела на некоторых новых устройствах (в книжной и альбомной ориентации показатели меняются местами). Мы уже видели эффект от применения различных разрешений экрана и его физических размеров: рисование с учетом абсолютных координат и размеров в пикселах может привести к самым разным результатам.

На рис. 5.1 изображено, что происходит, когда мы визуализируем прямоугольник 100 х 100 пикселов, верхний угол которого находится в точке (219; 379) на экранах 480 х 800 и 320 х 480.

Рис. 5.1. Прямоугольник 100 х 100 пикселов, верхний угол которого находится в точке (219;379) на экранах 480 х 800 (слева) и 320 х 480 (справа)

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

Плотность

Как правило, плотность определяется в пикселах на дюйм или пикселах на сантиметр (иногда вы можете встретить выражение точка на дюйм, но это не совсем верно). У Nexus One экран 480 х 800 пикселов при физическом размере 8 х 4,8 см. НТС Него имеет экран 320 х 480 пикселов при физическом размере 6,5 х 4,5 см. Таким образом, 100 пикселов на сантиметр по обеим осям в Nexus One – это примерно 71 пиксел на сантиметр по обеим осям в НТС Него. Мы можем легко подсчитать пикселы на сантиметр с помощью следующего уравнения:

Пикселы на сантиметр (по оси х) = ширина в пикселах / ширина в сантиметрах или этого:

Пикселы на сантиметр (по оси у) = высота в пикселах / высота в сантиметрах

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

Какого размера будет наш прямоугольник 100 х 100 пикселов в сантиметрах? На Nexus One у нас будет прямоугольник 1×1 см, а на НТС Него – 1,4 х 1,4 см.

Помните об этом, когда будете отрисовывать такие объекты, как кнопки, которые должны быть достаточно большими, чтобы на кнопку можно было нажать пальцем и это легко происходило на экранах любого размера. Хотя на первый взгляд кажется, что данная особенность может представлять большую проблему для разработчика, на самом деле это не так. Все, что нужно, – удостовериться, что кнопки имеют достаточно большой размер на экранах с большой плотностью, таких как Nexus One. Они будут автоматически увеличиваться на экранах с меньшей плотностью.

Соотношение сторон

Еще одна проблема, с которой нам предстоит справиться, – соотношение сторон. Соотношение сторон экрана – это отношение между шириной и высотой экрана в пикселах или сантиметрах. Оно подсчитывается следующим образом:

Соотношение сторон в пикселах = ширина в пикселах / высота в пикселах или так: Физическое соотношение сторон = ширина в сантиметрах / высота в сантиметрах

Употребляя слова ширина и высота, мы, как правило, говорим о ширине и высоте в ландшафтной ориентации. Соотношение сторон в Nexus One, физическое и в пикселах, примерно равно 1,66. У НТС Него физическое и пиксельное соотношение сторон равно 1,5. Что это значит? На Nexus One мы имеем в своем распоряжении больше пикселов по оси х в альбомной ориентации по сравнению с той высотой, которая доступна на НТС Него. На рис. 5.2 показано, как выглядит игра Replica Island на обоих устройствах.

Рис. 5.2. Replica Island на Nexus One (сверху) и НТС Него (снизу)

ПРИМЕЧАНИЕ

В этой е используется метрическая система.

На Nexus One чуть-чуть больше обзор по оси х. Однако по оси у все выглядит точно так же. Что же создатели Replica Island здесь сделали?

Как справляться с различными соотношениями сторон

Replica Island использует достаточно простую, но весьма эффективную хитрость для того, чтобы справиться с проблемой соотношения сторон. Изначально игра создавалась для экрана 480 х 320 пикселов, включая все спрайты (например, робота и доктора), сам мир и элементы пользовательского интерфейса (например, кнопки внизу слева и информацию о статусе вверху экрана). Когда игра отображается на НТС Него, каждый пиксел спрайта в растровом отображении соответствует одному пикселу на экране. На Nexus One во время визуализации все масштабируется, так что 1 пиксел спрайта в растровом отображении соответствует 1,5 пиксела на экране. Иными словами, персонаж 32 х 32 пиксела будет на экране иметь размер 48 х 48 пикселов. Коэффициент масштабирования можно подсчитать с помощью следующих формул:

Коэффициент масштабирования (по оси х) = ширина экрана в пикселах / целевая ширина в пикселах и Коэффициент масштабирования (по оси у) = высота экрана в пикселах / целевая высота в пикселах

Целевые ширина и высота равны разрешению экрана, для которого было разработано приложение. Так, к примеру, в Replica Island это 480 х 320 пикселов. В случае с Nexus One это значит, что коэффициент масштабирования по оси х равен 1,66, а по оси у – 1,5. Но почему коэффициент масштабирования на разных осях различный?

Дело в том, что экраны с разными разрешениями имеют различные соотношения сторон. Если мы просто растянем изображение 480 х 320 пикселов до изображения в 800 х 480 пикселов, оригинальная картинка будет растянута по оси х. Для большинства игр это будет некритично, так что мы просто нарисуем нужные графические объекты для определенного разрешения и растянем их до реального разрешения экрана во время визуализации (вспомните метод Bitmap.drawBitmap).

Для некоторых игр тем не менее придется немного потрудиться. На рис. 5.3 показана просто отмасштабированная с 480 х 320 до 800 х 480 пикселов Replica Island. Кроме того, наложено полупрозрачное изображение того, как это выглядит в действительности.

Создатели Replica Island использовали здесь весьма тонкий прием: мы видим нормальное растяжение по у-оси с коэффициентом масштабирования, который мы только что вычислили (1,5). Но вместо того, чтобы использовать коэффициент масштабирования (1,66), который сделает изображение более сжатым, здесь применяется коэффициент масштабирования у-оси. Подобная уловка позволяет всем объектам на экране сохранить соотношение сторон. Спрайт размером 32 х 32 пиксела увеличивается до 48 х 48 пикселов, а не 53 х 48 пикселов. Однако это также значит, что наша система координат больше не ограничивается (0; 0) и (479; 319). Вместо этого теперь ее границы (0; 0) и (533; 319). Именно поэтому мы видим больше игрового пространства Replica Island на Nexus One, чем на НТС Него.

Рис. 5.3. Replica Island, растянутая с 480 х 320 до 800 х 480 пикселов с наложенным полупрозрачным изображением того, как это выглядит в действительности на экране 800 х 480

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

Решение попроще

У Replica Island есть одно преимущество: здесь растяжение и масштабирование выполнены с помощью OpenGL ES, которое поддерживает аппаратное ускорение. Пока мы обсудили только, как нарисовать Bitmap и View с помощью класса Canvas, который не использует графический процессор, но применяет более медленный в таких ситуациях центральный процессор.

Теперь применим небольшую уловку: создадим фреймбуфер в виде экземпляра класса Bitmap, имеющего нужное нам разрешение. Таким образом, нам не придется волноваться о реальном разрешении экрана, когда мы будем создавать наши графические объекты или когда будем их визуализировать. Давайте предположим, что разрешение экрана одинаково на всех устройствах. Когда мы закончим визуализацию фрейма нашей игры, мы просто нарисуем этот фреймбуфер Bitmap размером с SurfaceView с помощью метода Canvas .drawBitmap, который позволяет изобразить растянутый Bitmap.

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

Реализация

Подведем промежуточные итоги, составив план действий.

Создаем графические объекты для фиксированного целевого разрешения (320 х 480 в случае с Мистером Номом).

Создаем Bi tmap такого же размера, как и наше целевое разрешение, и направляем все вызовы отрисовки к нему, работая в фиксированной системе координат.

Когда закончим рисовать кадр игры, рисуем фреймбуфер Bi tmap, растянутый до размера SurfaceView. На устройствах с меньшим разрешением масштаб изображения уменьшится, на устройствах с большим разрешением – увеличится.

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

Теперь, когда мы уже знаем, как обращаться с различными разрешениями и плотностями экрана, можно обсудить переменные sealе Х и seale Y, с которыми мы встречались, когда реализовывали обработчики Si ngl eTouchHandl ег и Mul ti touchhandl ег несколько страниц назад.

Весь код нашей игры будет ориентирован на работу с фиксированным разрешением (320 х 480 пикселов). Если мы получаем события касания на устройствах, которые имеют более высокое или более низкое разрешение, х- и /-координаты данных событий будут описаны в системе координат Vi ew, а не в системе координат нашего целевого разрешения. Таким образом, необходимо перевести координаты из оригинальной системы в нашу систему, основанную на коэффициенте масштабирования. Вот как это можно сделать:

Преобразованное касание по оси х = реальное касание по оси х   (целевое количество пикселов по оси х / реальное количество пикселов по оси х)

Преобразованное касание по оси у = реальное касание по оси у   (целевое количество пикселов по оси у / реальное количество пикселов по оси у)

Рассмотрим простой пример, где целевое разрешение равно 320 х 480 пикселов, а разрешение устройства – 480 х 800 пикселов. Если мы дотронемся до середины экрана, то получим координаты (240; 400). Используя две предыдущие формулы, получаем следующие данные, которые являются серединой нашей целевой системы координат:

Преобразованное касание по оси х = 240   (320 / 480) = 160 Преобразованное касание по оси у = 400   (480 / 800) = 240

Решим еще один пример, в котором реальное разрешение равно 240 х 320, а экрана снова коснулись в середине (120; 160):

Преобразованное касание по оси х = 120   (320 / 240) = 160 Преобразованное касание по оси у = 160   (480 / 320) = 240

Как видите, данный прием работает в обоих направлениях. Если мы умножим координаты реального события касания на целевой коэффициент, разделенный на реальный коэффициент, нам не придется отвлекаться на все эти преобразования в коде игры. Все координаты касаний будут отображены в нашей фиксированной целевой системе координат.

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

AndroidPixmap: пикселы для каждого

Согласно проекту интерфейса нашего Pixmap нам осталось реализовать не так уж много. Рассмотрим код в листинге 5.12.

Листинг 5.12. AndroidPixmap.Java, реализация Pixmap package com.bad.ogiс.androidgames.framework.imp.

Все, что нам нужно сделать, – сохранить экземпляр класса Bitmap, а также его формат, который хранится в виде одного из значений перечисления PixmapFormat. Дополнительно мы реализуем необходимые методы интерфейса Pixmap, чтобы можно было запрашивать ширину и высоту Pixmap и его формат, а также удостовериться, что пикселы извлекаются из оперативной памяти. Обратите внимание, что поле bitmap является приватным, так что у нас есть к нему доступ из Androi dGraphics, который мы сейчас и реализуем.

AndroidGraphics: то, что нужно для рисования

Интерфейс Graphi cs, достаточно компактный и эффективный. Он может рисовать пикселы, линии, прямоугольники и Pi xmap в фреймбуфере. Как мы уже говорили, мы будем использовать Bitmap в качестве нашего фреймбуфера и рисовать на нем с помощью Canvas. Он также отвечает за создание экземпляров класса Pi xmap из файла ресурсов. Таким образом нам снова понадобится AssetManager. В листинге 5.13 показан код реализации интерфейса Androi dGraphics.

Листинг 5.13. AndroidGraphics.Java: реализуем графический интерфейс package com.badlogiс.androidgames.framework.imp.

Данный класс реализует интерфейс Graphics. Он содержит член AssetManager, который нам нужен для того, чтобы загружать экземпляры Bitmap, член Bitmap, представляющий собой искусственный фреймбуфер, член Canvas, используемый для того, чтобы нарисовать искусственный фреймбуфер, член Paint, который необходим для рисования, и два члена Rect, которые нам понадобятся для реализации

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

В конструкторе получаем AssetManager и Bitmap, которые представляют наш искусственный фреймбуфер. Сохраняем их в соответствующих полях и дополнительно создаем экземпляр класса Canvas, который будет рисовать в искусственном фреймбуфере, а также Paint, который мы используем для некоторых методов рисования.

Метод newPixmapO загружает Bitmap из файла объектов, используя заданный PixmapFormat. Мы начинаем с того, что переводим PixmapFormat в одну из констант класса Android Config. Далее создаем экземпляр класса Options и устанавливаем предпочтительный формат цвета. После этого загружаем Bi tmap из ресурса с помощью BitmapFactory. Если что-то идет не так, генерируется исключение Runt imeExcept ion. В противном случае мы проверяем, в каком формате фабрика BitmapFactory решила загрузить Bitmap, и переводим его в значение перечисления PixmapFormat. Помните, что BitmapFactory может решить игнорировать наш предпочитаемый формат цвета, так что необходимо будет проверить, как она закодировала изображение. Наконец мы создаем новый экземпляр класса AndroidBitmap, основанный на экземпляре Bitmap, который мы загрузили, и PixmapFormat, а затем возвращаем этот новый экземпляр вызывающей стороне.

Метод clearO просто извлекает красный, зеленый и синий компоненты из определенного 32-битного ARGB цветового параметра и вызывает метод Canvas drawRGB, который очищает наш искусственный фреймбуфер с этим цветом. Этот метод не учитывает никакие альфа-значения определенного цвета, так что нам не надо его извлекать.

Метод drawPixel  рисует пиксел в нашем искусственном фреймбуфере с помощью метода Canvas drawPoint О. Для начала устанавливаем цвет в поле класса paint и передаем эти данные методу рисования в дополнение к х- и у-координатам пиксела.

Метод drawLi ne рисует линию в искусственном фреймбуфере, также используя поле paint, чтобы задать цвет, который будет применяться при вызове Canvas. drawLine.

Метод drawRect сначала устанавливает цвет в Paint и атрибут стиля, чтобы мы могли нарисовать заполненный цветом прямоугольник. Для самого вызова Canvas. drawRect нам потребуется преобразовать параметры х, у, width и height в координаты верхнего левого и нижнего правого углов прямоугольника. Для верхнего левого угла просто используем параметры х и у. Для координат нижнего правого угла добавляем ширину и высоту к х и у и отнимаем 1. Для примера представьте, что мы визуализируем прямоугольник, где х и у имеют координаты (10; 10), а ширина и высота равны 2. Если мы не отнимем 1, в итоге прямоугольник на экране будет размером 3×3 пиксела.

Метод drawPixmap, позволяющий нарисовать часть Pixmap, сначала устанавливает исходный прямоугольник и прямоугольник назначения в соответствующие поля, которые используются для вызова метода рисования. Поскольку мы рисуем прямоугольник, нам будет нужно перевести координаты х и у, а также ширину и высоту в координаты левого верхнего и правого нижнего углов. Мы снова отнимаем 1, иначе у нас получится один лишний пиксел. Далее выполняем рисование с помощью метода Canvas .drawBitmap, который также автоматически выполнит смешивание, если Pixmap, который мы рисуем, имеет глубину цвета PixmapFormat. ARGB4444 или PixmapFormat.ARGB8888. Обратите внимание, что нам необходимо привести параметр типа Pixmap к типу AndroidPixmap, чтобы мы могли выбрать член bitmap для рисования с помощью Canvas. Так делать не рекомендуется, но в данном случае мы можем быть уверены что переданный экземпляр Pixmap действительно будет представлять собой AndroidPi xmap.

Второй метод drawPixmap просто рисует весь Pixmap в искусственном фреймбуфере в заданных координатах. Мы снова используем приведение для того, чтобы добраться до члена bitmap класса AndroidPixmap.

Наконец, у нас есть методы getWidth и getHei ght , которые просто возвращают размер искусственного фреймбуфера, хранящего и отображающего экземпляр класса Androi dGraphi cs.

Источник: Mario Zechner / Марио Цехнер, «Программирование игр под Android», пер. Егор Сидорович, Евгений Зазноба, Издательство «Питер»

По теме:

  • Комментарии