в главе 6, дают возможность
Высокоуровневые классы, изученные в главе 6, дают возможность создавать пользовательский интерфейс приложения. По сути, эти классы выполнены в виде шаблонов, используя которые вы мoжете создавать списки, формы, шрифт, группы элементов, бегущие строки. Но использование таких классов-шаблонов несколько упрощает интерфейс программы, лишая возможности использования графики в программах на Java 2 ME. Иногда в приложении необходимо нарисовать таблицу, линию, квадрат, то есть воспользоваться графикой для создания насыщенной и красочной программы. Для этих целей в платформе Java 2 ME существуют так называемые классы низкоуровневого интерфейса - это классы Canvas и GameCanvas (класс GameCanvas будет рассмотрен в следующей главе), а так же класс Graphics, с помощью которого осуществляется непосредственная прорисовка графики на экране телефона. В самом начале этой главы дается характеристика классам Canvas и Graphics, попутно рассматриваются имеющиеся в составе обоих классов методы, после чего мы приступим к практической части и создадим ряд примеров, иллюстрирующих работу обоих классов.
Класс Canvas
Класс Canvas - это абстрактный класс, поэтому необходимо создавать подклассы для работы с классом Canvas. Абстрактный класс Canvas представляет некий обобщенный графический контекст, что позволяет программе производить прорисовку графики при помощи класса Graphics. Кроме этого класс Canvas предоставляет возможность в обработке событий полученных с клавиш телефона. Если классы высокоуровневого интерфейса, рассмотренные в главах 5 и 6, обрабатывают команды перехода, то-с помощью класса Canvas можно получать события с любой нажатой клавиши телефона.
Существует ряд так называемых «ключевых кодов» в виде заданных констант, с помощью которых можно назначать игровые действия для клавиш телефона. Все ключевые коды соответствуют стандарту ITU-T и заданны в виде следующих констант:
static int DOWN — движение вниз; static int FIRE - обычно используется в играх и реализует стрельбу из оружия; static int GAME_A — игровая клавиша А; static int GAME_B - игровая клавиша В; static int GAME_C - игровая клавиша С; static int GAME_D - игровая клавиша D; static int KEY_NUMO - клавиша 0; static int KEY_NUM1-клавиша 1; static int KEY_NUM2 - клавиша 2; static int KEY_NUM3 - клавиша 3; static int KEY_NUM4 - клавиша4; static int KEY_NUM5 - клавиша 5; static int KEY_NUM6 - клавиша 6; static int KEY_NUM7 - клавиша 7; static int KEY_NUM8 - клавиша 8; static int KEY_NUM9 - клавиша 9; static int KEY_POUND - клавиша #; static int KEY_STAR - клавиша *; static int LEFT - движение влево; static int RIGHT - движение вправо; static int UP - движение вверх.
Ключевые коды GAME_A, GAME_B, GAME_C, GAME_D и FIRE предназначены специально для игровых действий и обычно задаются клавишам с цифрами соответственно 2,4,8,6 и 5, но зависят от реализации конкретных моделей телефонов.
Методы класса Canvas
Большинство методов класса Canvas обеспечивают обработку низкоуровневых событий. Абстрактный метод void paint (Graphics g) является основным методом, с помощью которого происходит прорисовка графики на экране телефона. Класс Graphics определяет, что именно необходимо рисовать на экране телефона. Разберем основную часть методов класса Canvas:
int getGameAction(int keyCode) - связывает игровые действия с заданным ключевым кодом; int getKeyCode(int gameAction) - получает ключевой код игровых действий; String getKeyName (int keyCode) - получает ключевой код для клавиши; boolean hasPointerMotionEvents () - проверяет поддержку устройством перемещение указателя; protected void keyPressed( int keyCode) - вызывается при нажатии клавиши; protected void keyReleased(int keyCode) - вызывается при отпускании нажатой клавиши; protected void keyRepeated(int keyCode) - повторное нажатие клавиши; protected abstract void paint (Graphics g) - прорисовка графики на экране телефона; protected void pointerDragged(int x, int у) - определяет перемещение курсора; protected void pointerPressed(int x, int у) -определяет позицию курсора, при которой должно производится нажатие определенной клавиши; protected void pointerReleased(int x, int у) -определяет Позицию курсора в момент отпускания определенной клавиши; void repaint () - повторяет прорисовку; void repaint(int x, int у, int width, int height)-повторяет прорисовку заданной области.
Класс Graphics
При помощи класса Graphics осуществляется двухмерное представление графики на экране телефона. Класс Graphics существует также в составе Java 2 SE, но в платформе Java 2 ME он сильно урезан, всвязи с ограниченными системными ресурсами телефонов. Поэтому имеется возможность рисовать только линии, прямоугольники, дуги и текст. Для окрашивания этих примитивов предусмотрена работа с цветовой компонентой.
Система координат, используемая для представления графики в Java 2 ME, перевернута по отношению к обычной Декартовой системе координат. Начало системы координат, находится в левой верхней точке экрана телефона. Положительная ость X проходит по верхней кромке экрана слева направо, а положительная ось Y- сверху вниз по левой стороне экрана, как изображено на рис. 7.1.
Такая система координат отнюдь не новшество в программировании двухмерной графики. Идентичная модель координат применяется так же в DirectX и OpenGL для представления двухмерных сцен, но уже в компьютерной графике.
Рис. 7.1. Система координат в Java 2 ME
Методы класса Graphics
Основные методы класса Graphics обеспечивают прорисовку двухмерной графики. Есть еще несколько методов, с помощью которых можно произвести перемещение системы координат и произвести отсечение (clipping). Основные методы класса Graphics:
void copyArea(int x_src, int y_src, int width, int height, int x_dest, int y_dest, int anchor)- копирует прямоугольную область из установленных значений в параметрах (x_src, y_src, width, height), в новую область (x_dest, y_dest); void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) - рисует контур дуги в виде эллипса; void drawChar(char character, int x, int y, int anchor) -рисует символ; void drawChars(char[] data, int offset, int length, int x, int y, int anchor) - рисует массив символов: void drawlmage (Image img, int x, int y, int anchor) -рисует изображение; void drawLine(int x1, int y1, int x2, int y2) - рисует линию из точки x1и y1, до точки х2 и у2; void drawRegion(Image src, int x_src, int y_src, int width, int height, int transform, int x_dest, int y_dest, int anchor) - копирует изображения в заданную область на экран телефона; void drawRoundRect(int x, int у, int width, int height, int- arcWidth, int arcHeight) — рисует контур прямоугольника, используя закругленные углы; void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) - рисует заполненную цветом дугу; void fillRect(int x, int у, int width, int height)- рисует заполненный цветом прямоугольник; void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) - рисует заполненный прямоугольник, используя закругленные углы; void fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3) - рисует заполненный цветом треугольник; int getBlueComponent () - получает синий компонент цвета; int getClipHeight () -получает высоту для текущей области отсечения; int getClipWidth() -получает ширину для текущей области отсечения; int getColor() - получает текущий цвет; Font getFont() - получает текущий шрифт; int getGreenComponent () -получает зеленный компонент цвета; int getRedComponent ()- получает красный компонент цвета; void setClip(int x, int y, int width, int height)-устанавливает отсечение заданной области экрана; void setColor (int RGB) - устанавливает цвет при помощи значения RGB; void setColor (int red, int green, int blue) - назначает цвет при помощи трех цветовых компонентов red, green и blue; void setFont (Font font) - устанавливает заданный шрифт; void setStrokeStyle (int style) - задает штриховой стиль рисуемому контексту, используя константы SOLID и DOTTED; void translate (int x, int у) - перемещает систему координат в точку х и у.
При использовании некоторых методов, очень часто используется параметр int anchor. С помощью этого параметра задаются различные значения для выбора позиции. Посмотрите на рис. 7.2, где изображен механизм прорисовки текста с выбором определенной позиции.
Рис. 7.2. Техника прорисовки текста
Для этих целей в классе Graphics имеются константы, с помощью которых происходит выбор позиции:
static int BASELINE - задает базовую линию; static int BOTTOM - сдвигает вниз; static int HCENTER - центрирует; static int LEFT - сдвигает влево; . static int RIGHT - сдвигает вправо; static int TOP - сдвигает вверх; static int VCENTER - используется только при прорисовке изображений, производит вертикальную центровку всего изображения.
Можно использовать две константы для выбора позиции. Например, для того чтобы сдвинуть текст влево и вверх, используется комбинация Graphics. LEFT I Graphics .TOP.
Далее мы перейдем к практике и изучим модель программирования графики в приложении на Java 2 ME, рассмотрим создание и отрисовку линий, прямоугольников, дуг и текста. Главное о чем надо помнить при использовании графических элементов - это о размере экрана телефона. Разные модели телефонов имеют свои размеры дисплея, и если вы будете использовать большой по площади экран, например 128x128 пикселей, то на экране с разрешением 101У80, некоторые части графических элементов будут срезаны. Чтобы этого избежать, надо использовать методы класса Canvas, getwidth () и getHeight (), которые возвращают размеры ширины и высоты экрана и уже на основании этих данных производить построение графических элементов, производя тем самым адаптацию графического контекста к конкретной модели телефона. Например, чтобы нарисовать, горизонтальную линию, не выходящую из зоны видимости, можно воспользоваться следующим кодом:
int w = getWidth.{); drawLine(20, 20, w-20, w-20);
В своих примерах к этой главе я специально не использую оптимизации графики, для того чтобы исходный код был более понятен. Поэтому после компиляции всех примеров обязательно запустите получившиеся программы на максимальном количестве имеющихся эмуляторов. Особенно попробуйте работу этих программ на эмуляторе DefaultColorPhone из состава среды программирования J2ME Wireless Toolkit 2.1, меня лично очень сильно позабавил результат работы этого эмулятора, а вам, я думаю, предоставит некоторую пищу для размышлений.
Рисование линий
Для того чтобы нарисовать линию нужно воспользоваться методом draw-Line () класса Graphics. Рассмотрим прототип этого метода:
public void drawLine(int x1,
int y1,
int x2,
int y2)
Параметры метода drawLine ():
x1 - координата начальной точки отрезка по оси X; у1 - координата начальной точки отрезка по оси Y; х2 - координата конечной точки отрезка по оси X; у2 - координата конечной точки отрезка по оси Y.
Задавая целочисленные координаты точек в методе drawLine () можно нарисовать любую линию заданного размера. Также можно воспользоваться методом setColor() для закрашивания линий цветом и методом setStrokeSty le () -для рисования линии в виде пунктира.
Рассмотрим пример кода находящийся в листинге 7.1, где с помощью линий рисуется система координат используемая в Java 2 ME.
/ * *
Листинг 7.1
Класс Main и класс Line
*/
import javax..microedition.lcdui .*;
import javax.microedition.midlet.*;
public class Main extends MIDlet implements
CommandListener
{
// команда выхода из программы
private Command
exitMidlet = new Command("Выход",Command.EXIT, 0);
public void startApp()
{
// создаем объект класса Line
Line myline = new Line();
// добавляем команду выхода
myline.addCommand(exitMidlet);
myline.setCommandListener(this);
Display.getDisplay(this).setCurrent(myline);
}
public void pauseApp() {}
public void destroyApp(boolean unconditional){}
public void commandAction(Command c, Displayable d)
{
if (c = = exitMidlet)
{
destroyApp(false); notifyDestroyed();
}
}
}
/**
класс Line определен в файле Line.Java
*/
import javax.microedition.Icdui.*;
public class Line extends Canvas
{
// конструктор
public Line(){ super();
}
public void paint(Graphics g)
{
// устанавливается синий цвет для линий
g.setColor(0x0000ff);
// рисуется система координат
g.drawLine(20, 20, 90, 20);
g.drawLine(20, 20, 20, 90)
g.drawLine(90, 20, 80, 10)
g.drawLine(90, 20, 80, 30)
g.drawLine(20, 90, 10, 80)
g.drawLine(20, 90, 30, 80)
// устанавливается красный цвет для трех линий
g.setColor(0xffff0000);
// рисуем толстую линию, в три пикселя
g.drawLine(30, 30, 70, 30);
g.drawLine(30, 31, 70, 31);
g.drawLine(30, 32, 70, 32);
// устанавливаем пунктир
g.setStrokeStyle(Graphics.DOTTED) ;
// рисуем линию в два пикселя пунктиром
g.drawLine(30, 50, 70, 50);
g.drawLine(30, 51, 70, 51);
}
}
В листинге 7.1 используются два класса: Main, являющийся основным классом мидлета приложения, и класс Line, в котором происходит работа с графикой. Подразумевается, что оба класса находятся в файлах Main.java и Line.java, это характерно для объектно-ориентированного программирования и в дальнейшем мы будем придерживаться именно такой модели построения изучаемых примеров. Основной класс мидлета Main очень прост. В этом классе создается объект класса Line, добавляется команда выхода из приложения и отражается текущий экран. Класс Line, находящийся в файле Line.java листинга 7.1, рисует набор различных линий. Сам класс Line наследуется от абстрактного класса Canvas. В более сложных программах может использоваться интерфейс Run-nable и метод run (). Такая техника программирования обычно используется при создании игр, и будет обсуждаться в конце этой главы.
Конструктор класса Line использует метод super () позволяющий обратиться к конструктору своего суперкласса Canvas. Основные же события происходят в методе paint () класса Canvas.
В строке кода:
g.setColor(OxOOOOff)
происходит назначение цвета для любого последующего отрисованного примитива. То есть вызов метода setColor () с заданным цветом действителен до момента последующего вызова метода setColor (), устанавливающего другой цвет. В этом примере используется метод setColor (RGB), поэтому значение цвета задается восьмеричным значением с помощью нулей и букв. В следующем примере из раздела 7.4 при рисовании прямоугольников будет показана работа метода setColor () с тремя целочисленными параметрами, задающими значение цвета.
В строках кода
g.drawLine(20, 20, 90, 20)
g.drawLine(20, 20, 20, 90)
g.drawLine(90, 20, 80, 10)
g.drawLine(90, 20, 80, 30)
g.drawLine(20, 90, 10, 80)
g.drawLine(20, 90, 30, 80)
рисуется шесть синих линий, образующих систему координат. Толщина всех линий равна одному пикселю - это значение по умолчанию и изменить его нельзя. Для того чтобы нарисовать широкую линию, придется рисовать несколько соприкасающихся одинаковых по размеру линий.
g.drawLine(30, 30, 70, 30);
g.drawLine 30, 31, 70, 31);
g.drawLine(30, 32, 70, 32);
Этими строками кода рисуется одна толстая линия шириной в три пикселя.
В конце в методе paint () рисуется линия толщиной в два пикселя в виде пунктирной линии. Для этого используется метод setStrokeStyle () и константа DOTTER. На рис. 7.3 изображен эмулятор телефона с результатом работы программы из листинга 7.1.
Рис. 7.3. Рисование разноцветных линий
Рисование прямоугольников
При создании прямоугольников можно использовать два метода класса Graphics- это drawRect() и fillRect (). При помощи метода drawRect () рисуется только контур прямоугольника, а метод fillRect () позволяет нарисовать прямоугольник уже закрашенным каким-либо цветом (изначально по умолчанию цвет черный). Оба метода абсолютно идентичны по количеству и назначению параметров, поэтому рассмотрим прототип одного из них, а именно метода drawRect ().
public void drawRect(int x,
int y,
int width,
int height)
Параметры метода drawRect ():
x - координата точки по оси X для левого верхнего угла прямоугольника; у - координата точки по оси Y для левого верхнего угла прямоугольника; width - ширина рисуемого прямоугольника; height - высота рисуемого прямоугольника.
В составе класса Graphics имеется еще один метод рисующий прямоугольник, но с закругленными углами - drawRoundRqct (). Этот метод имеет уже шесть параметров, где первые четыре параметра работают в том же ключе что и методы drawRect () и fillRect (), а два последних параметра определяют степень закругленности углов. В листинге 7.2 рисуется три разных по размеру прямоугольника, и закрашиваются тремя цветами: красным, зеленым и синим. Для закрашивания прямоугольников применяется метод setColor (int red, int green, int blue) с тремя параметрами. Выставляя любое целочисленное значение от 0 до 255 для каждого параметра можно создавать разнообразную цветовую гамму, естественно учитывая при этом цветовые возможности телефона, на котором будет работать эта программа. Для того чтобы определить доступную цветность дисплея телефона, необходимо воспользоваться методами класса Display:
isColor () - если телефон поддерживает цветовую гамму, то возвращает значение true; numColor() - определяет количество доступных цветов.
/**
Листинг 7.2
Класс Main и класс Rectangles
*/
import javax. micro-edit ion. Icdui.*;
import javax.microedition.midlet.*;
public class Main extends MIDlet implements CommandListener
{
// команда выхода из программы
private Command exitMidlet = new Command("Выход", Command.EXIT, 0);
public void startAppO {
// создаем объект класса Rectangles
Rectangles myrec = new Rectangles();
// добавляем команду выхода
myrec. addCommand( exitMidlet);
myrec.setCommandListener(this);
Display.getDisplay(this).setCurrent(myrec);
}
public void pauseApp() {}
public void destroyApp(boolean unconditional){}
}
public void coiranandAction(Command c, Displayable d)
{
if (c == exitMidlet)
{
destroyApp (false) ,notifyDestroyed();
}
}
}
/ * *
класс Rectangles определен в файле Rectangles.Java,
*/
import javax..microedition. Icdui ;
public class Rectangles extends Canvas
{
// конструктор
public Rectangles(){ super();
}
public void paint(Graphics g)
{
// устанавливается красный цвет
g.setColor(255, 0,0);
// рисуем первый прямоугольник
g.fillRect(/*x*/ 15,/*у*/ 30,/*ширина*/ 15,/*высота*/ 20);
// устанавливается зеленный цвет
g.setColor(0, 255, 0);
// рисуем второй прямоугольник
g.fillRect(30, 30, 15, 45);
// устанавливается синий цвет
g.setColor(0, 0, 255);
// рисуем третий прямоугольник
g.fillRect(45, 30, 15, 60);
// устанавливается синий цвет
g.setColor(255, 0, 0);
// рисуем прямоугольник с закругленными углами
g.drawRoundRect(70, 30, 40, 40, 10, 10);
}
}
В этом примере также используются два класса - класс Main, играющий роль основного класса мидлета и класс Rectangles, где происходит отрисовка графики. Оба класса разделены на два файла Main.java и Rectangles.java. В классе Main создается объект класса Rectangles, добавляется команда выхода и показывается текущий экран. Класс Rectangles является подклассом класса Canvas. Прорисовка прямоугольников происходит в методе paint () класса Graphics.
g.setColor(255, 0, 0) ;
В этой строке кода устанавливается красный цвет для прямоугольника размером 15 на 20 пикселей, который рисуется с помощью метода fillRect ().
g.fillRect(/*x*/ 15,/*у*/30,/*ширина*/15,/*высота*/20);
Прямоугольник рисуется закрашенным в красный цвет. Дальше происходит прорисовка еще двух закрашенных в зеленый и синий цвет прямоугольников с размерами соответственно 15x45 и 15x60 пикселей.
В конце всего кода в классе Rectangles рисуется контур прямоугольника с закругленными углами. На рис. 7.4 изображен эмулятор телефона с четырьмя нарисованными прямоугольниками.
Рис. 7.4. Рисование четырех прямоугольников
Рисование дуг
В английском языке слово arc означает дугу, и именно это слово применяется в документации по Java 2 ME. Используя методы drawArc () и fillArc () можно нарисовать как дугу, так и полноценную окружность. Используя оба метода, как вы уже наверно заметили можно нарисовать контур дуги и закрашенную цветом дугу. Методы drawArc () и fillArc () имеют одинаковое количество параметров со сходными действиями. Рассмотрим один из методов - fillArc ().
public void fillArc(int x,
int y, int width,
int height,
int startAngle,
int arcAngle)
Параметры метода fill Arc():
x - расстояние, откладываемое от оси X до мнимой вертикальной касательной к окружности; у - расстояние, откладываемое от оси Y до мнимой горизонтальной касательной к окружности; wight - ширина рисуемой дуги (горизонтальный радиус); height - высота рисуемой дуги (вертикальный диаметр); startAngle - стартовый угол для начала рисования дуги; arcAngle - протяженность дуги (значение 360 замкнет дугу в окружность).
На рис. 7.5 изображена схематично техника создания дуги.
Рис. 7.5. Техника создания дуги
В листинге 7.3 приведен пример кода создающего три разноцветных сегмента круга, наложенных друг на друга, и дугу в виде контура.
/**
Листинг 7.3
Класс Main и класс Arc
*/
import javax.microedition. Icdui.*;
import javax.microedition.midlet.*;
public class Main extends MIDlet implements
CommandListener
{
// команда выхода из программы
private Command exitMidlet = new Command("Выход", Command.EXIT, 0)
public void startApp()
{
// создаем объект класса Arc
Arc myarc = new Arc();
// добавляем команду выхода
myarc.addCommand(exitMidlet);
myarc.setCommandListener(this);
Display.getDisplay(this).setCurrent(myarc);
}
public void pauseApp() {}
public void destroyApp(boolean unconditional){}
public void commandAction(Command c, Displayable d)
{
if (c = = exitMidlet)
{
destroyApp(false); notifyDestroyed();
}
}
/**
класс Arc определен в файле Arc.Java рисует дуги
*/
import javax.microedition.Icdui.*;
public class Arc extends Canvas
{
// конструктор
public Arc(){ super();
}
public void paint(Graphics g)
{
// устанавливается красный цвет
g.setColor(255, 0, 0);
// рисуем первую заполненную цветом дугу
g.fillArc(15, 15, 60., 60, 45, 360);
// устанавливается зеленый цвет
g.setColor(0, 255, 0) ;
// накладываем вторую дугу поверх первой
g.fillArc(15, 15, 60, 60, 45, 180);
// устанавливается синий цвет
g.setColor(0, 0, 255);
// накладываем третью дугу поверх первых двух
g.fillArc(15, 15, 60, 60, 45, 90);
// устанавливается синий цвет для дуги в виде контура
g.setColor(0, 0, 255);
// рисуем контур дуги
g.drawArc(5, 5, 80, 80, 30, 180);
}
}
В листинге 7.3 используется тот же самый механизм, что и в примерах из листингов 7.2 и 7.1 - создаются два класса: Main и Arc, находящиеся в файлах Main.java и Arc.java. Все действия по прорисовке дуг осуществляются в методе paint().
g.setColor(255, 0,0);
g.fillArc(15, 15, 60, 60, 45, 360);
В этих строках кода происходит установка цвета для рисуемой дуги и происходит прорисовка самой дуги. Первые два значения в методе fillArc () - 15 и 15 пикселей задают координаты точки в пространстве, относительно которой будет происходить прорисовка дуги. Значения 60 и 60 пикселей задают ширину и высоту дуги. Значением 45 устанавливается угол для начала рисования дуги (со значением 360 будет нарисована замкнутая окружность). Затем в примере рисуются еще два сегмента зеленого и синего цвета, наложенные поверх первой нарисованной дуги.
Вывод текста
Для вывода текста на экран телефона можно воспользоваться методами drawstring () и drawChar (), рисующими соответственно строка-текста и любой назначенный символ. Текст можно выводить с любым цветом, а также использовать стили начертания, изученные в главе 6. Прототип метода drawstring () выглядит следующим образом:
public void drawstring (String str,
int x,
int y,
int anchor)
Параметры метода drawstring ():
str - строка текста; x и у - задают размер невидимого прямоугольника, в котором происходит расположение текста; anchor - в этом параметре задается выбор позиции текста внутри невидимого прямоугольника. Здесь используются константы класса Graphics, рассмотренные в разделе 7.2.
В листинге 7.4 показан пример вывода текста на экран телефона. Код довольно прост и я думаю, вам не составит труда разобраться в нем самостоятельно.
/**
Листинг 7.4
Класс Main и класс Text
*/
import javax.microedition.Icdui.*;
import javax.microedition.midlet.*;
public class Main extends MIDlet implements
CommandListener
{
// команда выхода из программы
private Command exitMidlet = new Command(«Выход», Command.EXIT, 0);
public void startApp()
{
// создаем объект класса Text
Text mytext = new Text();
// добавляем команду выхода
mytext.addCommand(exitMidlet);
mytext.setCommandListener(this);
Display.getDisplay(this).setCurrent(mytext);
}
public void pauseApp() {}
public void destroyApp(boolean unconditional){}
public void сommandAction(Command c, Displayable d)
{
if (c = = exitMidlet)
{
destroyApp(false) ;
notifyDestroyedf);
}
}
}
/**
класс Text определен в файле Text.Java рисует текст
*/
import javax.microedition.lcdui.*;
public class Text extends Canvas
{
// конструктор
public Text(){super();
}
public void paint(Graphics g)
{
// устанавливается цвет
g.setColor(10, 80, 200);
// рисуем строку текста
g.drawstring(«Java 2 Micro Edition»,
80, 40, Graphics.TOP | Graphics.HCENTER);
}
}
Механизм создания игрового цикла
Для создания цикла классом Canvas используется интерфейс Runnable и его единственный метод run (), в котором реализуется цикл прорисовки графики. Рассмотрим в качестве примера класс DemoGraphics и проанализируем его.
public class DemoGraphics extends
Canvas implements Runnable
{
public void run()
{
while(true)
{
// обновление графических элементов repaint();
// задержка цикла на 20 миллисекунд для
обновления состояния дисплея
Thread.sleep(20);
} }
public void paint( Graphics g )
{
// код, создающий графические элементы
}
public void keyPressed( int keyCode )
{
// обработка событий с клавиш телефона
при помощи ключевых кодов
}
}
В классе DemoGraphics для упрощения опущен основной код, связанный с прорисовкой графики и обработкой событий, поступающих с клавиш телефона. Весь цикл прорисовки графики состоит из трех методов: run (), paint () и keyPressed().
В методе paint () происходит создание и отрисовка графических элементов программы на экране телефона, а метод run () создает цикл, в котором происходит постоянное обновление экрана телефона, связанное, например, с движением графического элемента. События, полученные в результате нажатия клавиш, поступают в метод key Pressed (), где обрабатываются заданным вами способом. На основании этих событий также может происходить обновление цикла прорисовки графики.
В методе run () создается бесконечный цикл while (true). В реальной программе необходимо, конечно, предусмотреть выход из бесконечного цикла. Метод repaint () постоянно обновляет графические элементы. Метод sleep () класса Thread останавливает системный поток на двадцать миллисекунд, для того чтобы отреагировать на все произошедшие изменения, а именно, произвести обработку событий с клавиш телефона и перерисовать графический элемент на экране телефона. Механизм прорисовки графики в профиле MIDP 1.0 строится именно по такому принципу , связанному с изменением состояния графических элементов и называмому игровым циклом.
Теперь давайте рассмотрим как можно больше примеров, реализующих вывод графики на экран, перемещение графических элементов, обработку событий с клавиш телефона, столкновение графического элемента с препятствием и других хитростей, из которых состоят мобильные игры.
Перемещение квадрата
Начнем с самого простого - выведем на экран синий квадрат, прорисованный с помощью метода fillRect() и заставим переместиться его через весь экран по горизонтали слева на право. Посмотрите на код из листинга 7.5, производящий перемещение квадрата на экране.
/ * *
Листинг 7.5
Класс Main и класс Draw
*/
import javax.micro-edition. Icdui .*;
import javax.microedition.midlet.*;
public class Main extends MIDlet implements
CommandListener
{
// команда выхода из программы
private Command
exitMidlet = new Command("Выход",
Command.EXIT, 0);
public void startApp()
{
// создаем объект класса Draw
Draw dr = new Draw();
// запускаем поток
dr.start();
// добавляем команду выхода
dr.addCommand(exitMidlet);
dr.setCommandListener(this);
Display.getDisplay(this).setCurrent(dr);
}
public void pauseApp() {}
public void destroyApp(boolean unconditional){}
public void commandAction(Command c, Displayable d)
{
if (с == exitMidlet)
{
destroyApp(false) ;
notifyDestroyed() ;
}
}
/**
класс Draw определен в файле Draw.Java перемещает квадрат по экрану
*/
import javax.microedition.lcdui.*;
public class
Draw extends Canvas implements Runnable
{
// позиция для перемещения квадрата
int position =10;
// конструктор
public Draw()
{ super(); }
public void start()
{
// создаем и запускаем, поток
Thread t = new Thread(this);
t.start();
}
// метод run() интерфейса Runnable
public void run()
{
// бесконечный цикл
while (true)
{
// увеличиваем позицию на 1
position ++;
// обновляем экран
repaint();
// останавливаем цикл на 20 миллисекунд
try { Thread.sleep(20);
}
catch (java.lang.InterruptedException zxz) {}
}
}
public void paint(Graphics g)
{
// вычисляем область для перерисовки экрана
int x = g.getClipWidth();
int у = g.getClipHeight ()
// устанавливаем белый цвет фона
g.setColor(0xffffff);
// назначаем перерисовку всему экрану
g.fillRect(0,0,х,у);
// устанавливается синий цвет квадрата
vg.setColor(0, 0, 200);
// рисуем квадрат
g.fillRect(position,40, 20, 20);
}
}
Листинг 7.5 содержит два класса Main и Draw, находящиеся в файлах Main.java и Draw.Java. Основной класс мидлета Main содержит код создающий объект dr класса Draw. Затем он запускает системный поток с помощью метода start (), добавляет команду выхода из программы и отображает текущий дисплей с объектом dr. Класс Draw содержит код, создающий синий квадрат и перемещающий его по экрану. Прежде чем мы начнем рассмотрение кода класса Draw, давайте подумаем, как можно произвести перемещение объекта на экране. Самый простейший способ перемещения объекта по экрану вдоль оси X, заключается в постоянном увеличении, допустим на единицу, позиции этого объекта по оси X. При создании квадрата методом fillRect () задаются две координаты по оси X и по оси Y для первоначального вывода квадрата на экран. Поэтому достаточно создать переменную для координаты по оси X и затем в цикле прорисовки увеличивать ее на единицу, перемещая тем самым объект по экрану.
В листинге 7.5 класс Draw наследуется от класса Canvas, что дает возможность воспользоваться Методом paint (). для рисования графических элементов на экране и. реализации метода run () интерфейса Runnable. В методе run () создается цикл, постоянно обновляющий состояние графических элементов.
В начале кода класса Draw создается переменная position, отвечающая за координату по оси X для точки вывода квадрата на экран. Конструктор класса Draw вызывает метод super () для использования конструктора своего суперкласса Canvas.
В методе start () создается системный поток, который будет запущен в методе run (). Кстати, было бы не плохо предусмотреть выход из потока. Этот пример небольшой и проблем с ним не возникнет, а после разбора листинга 7.5, мы рассмотрим простой механизм, останавливающий системный поток и предусматривающий выход из бесконечного цикла while метода run (). В самом методе run () создается бесконечный цикл, в котором происходит постоянное увеличение переменной position на единицу, благодаря чему квадрат перемещается по оси X слева направо.
В методе paint () вас должны заинтересовать следующие строки кода:
int х = g.getClipWidthf);
int у = g.getClipHeight();
g.setColor(0xffffff);
g.fillRect(0,0,x,y);
Здесь используется отсечение (clipping) необходимое для корректной прорисовки графики. Если не использовать отсечение, то после того как вы нарисуете квадрат и начнете передвигать его, на экране будет рисоваться слева на право одна толстая синяя линия. То есть будет происходить постоянная перерисовка квадрата в новой позиции по оси X, но при этом будет оставаться и предыдущий нарисованный квадрат. Чтобы этого избежать имеется два способа: можно стирать определенный участок экрана, а можно просто перерисовывать цвет фона всего экрана. Такая операция в Java 2 ME называется отсечением, и для произведения этой операции используются методы: getClipWigthf) и getClipHeight (), производящие отсечение поверхности всего экрана и методы getClipX () и getClipY () для более точного указания координат заданной области экрана для отсечения. По сути, использование этих методов приводит к простому обновлению всего экрана либо заданной области экрана. В этом примере мы будем использовать перерисовку всего фона экрана. Ширина и высота экрана узнается с помощью методов getClipWigth () и getClipHeight (), полученные значения сохраняются в переменных х и у. Вызовом метода setColor () устанавливается белый цвет фона и в следующей строке кода задается прямоугольник для области отсечения:
g.fillRect(0, 0, х, у );
В конце метода paint () рисуем синий квадрат:
g.fillRect(position, 40, 20, 20);
С каждым проходом через графический цикл, цвет фона будет перерисовываться и стирать прошлую позицию квадрата, а квадрат постоянно перерисовывается на новом месте в соответствии со значением переменной position, что и создает иллюзию передвижения объекта по экрану телефона.
После компиляции примера из листинга 7.5, на экране появится синий квадрат и пересечет один раз дисплей телефона слева направо. Теперь что касается бесконечного цикла while. В методе run () в реальном приложении необходимо предусмотреть выход из него, а так же позаботиться о прекращений работы потока. Самый простой способ заключается в создании переменной отвечающей за состояние цикла в начале класса Draw, например:
boolean z;
Дальше в методе start () присвоить этой переменной значение true.
public void start()
{
z = true;
Thread t = new Thread();
t.start();
}
А в методе run () использовать значение переменной z для входа в цикл
while
while(z)
{
// код цикла
}
Для логического конца создается новый метод stop () в классе Draw, назначая переменной z значения false.
public void stop()
{
z = false;
}
Вызовем этот метод в классе Main в методе destroyApp ():
public void destroyApp(boolean unconditional)
{
z.stop ();
}
Циклическое передвижение объекта по экрану
В листинге 7.5 был нарисован синий квадрат и перемещен один раз вдоль оси X горизонтально слева направо. Но иногда в играх необходимо циклично передвигать объект, используя его, например, в качестве мишени.
Возьмем за основу код из листинга 7.5, где перемещается квадрат, и сделаем так, чтобы после пересечения всего экрана и исчезновения, он снова появлялся с другой стороны, создавая подобие циклического перемещения. Алгоритм решения этой задачи заключается в том, чтобы узнать, когда квадрат выйдет из области видимости и в тот же момент нарисовать его с другой стороны экрана и вновь переместить по экрану. Для этого создадим переменную end и присвоим ей значение окончания экрана найденное методом getwidth () (движение происходит по ширине экрана).
int.end = getwidth();
В методе run () в самом начале цикла while будем производить постоянное сравнение позиции квадрата с окончанием экрана:
if (position > end)
{
position = 0;
}
Как только квадрат будет выходить из области экрана, его позиция обнулится и квадрат снова будет нарисован в первоначальной позиции, что зациклит движение квадрата. В листинге 7.6 представлен исходный код решающий эту задачу.
/**
Листинг 7.6
Класс Main и luiacq.Draw
*/
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
public class Main
extends MIDlet implements CommandListener
{
// команда выхода из программы
private Command
exitMidlet = new Command("Выход",Command.EXIT, 0);
public void startApp()
{
// создаем объект класса Draw
Draw dr = new Draw();
// запускаем поток
dr.start();
// добавляем команду выхода
dr.addCommand(exitMidlet);
dr.setCommandListener(this);
Display.getDisplay(this).setCurrent(dr);
}
public void pauseApp() {}
public void destroyApp(boolean unconditional){}
public void commandAction(Command c, Displayable d)
{
if (c == exitMidlet) {
destroyApp(false); notifyDestroyedt) ;
}
}
}
/**
класс Draw определен в файле
Draw.Java циклическое появление квадрата
*/
import javax.microedition.Icdui.*;
public class
Draw extends Canvas implements Runnable
{
// позиция для перемещения квадрата
int position = 10;
// узнаем ширину экрана
int end = getwidth();
// конструктор
public Draw()
{ super(); }
public void start ()
{
// создаем и запускаем поток
Thread t = new Thread(this);
t.start(); }
// метод run интерфейса Runnable
public void run()
{
// бесконечный цикл
while (true)
{
// сравниваем позицию квадрата
if(position > end)
{
// обнуляем позицию квадрата position = 0;
}
// увеличиваем позицию на 1
position ++;
// обновляем экран
repaint() ;
// останавливаем цикл на 20 миллисекунд
try { Thread.sleep(20); }
catch (Java. lang. InterruptedException zxz) {}
) }
public void paint(Graphics g)
{
// вычисляем область для перерисовки экрана
int x = g.getClipWidth();
int у = g.getClipHeight();
// устанавливаем белый цвет фона
g.setColor(0xffffff);
// назначаем перерисовку всему экрану
g.fillRect(0,0,х,у);
//устанавливается синий цвет квадрата
g.setColor(0, 0, 200);
// рисуем квадрат
g.fillRect(position,40, 20, 20);
}
}
Столкновение
В предыдущем разделе 7.9 мы выведи на экран синий квадрат, задали ему вектор движения и перемещали квадрат горизонтально через весь экран. После того как квадрат исчезал, достигнув конца экрана, он циклично появлялся вновь с другой стороны. Следующий пример иллюстрирует столкновение круга и квадрата с препятствием, а именно, окончанием экрана телефона. Оба объекта рисуются независимо друг от друга по высоте экрана и перемещаются параллельно по горизонтали слева на право. По достижению конца экрана оба объекта отталкиваются от конца экрана и начинают движение в обратном направлении. Посмотрите на листинг 7.7, где приводится код программы осуществляющий эти действия. Эта программа состоит из двух файлов Main.java и Draw.java.
/**
Листинг 7.7
Класс Main и класс Draw
*/
import javax.microedition.Icdui.*;
import javax.microedition.midlet.*;
public class Main extends MIDlet implements.
CommandListener
{
//команда выхода из программы
private Command exitMidlet = new Command("Выход", Command.EXIT, 0);
publicvoid startApp()
{
// создаем объект класса Draw Draw dr = new Draw();
// запускаем поток dr.start();
// добавляем команду выхода
dr.addCommand(exitMidlet) ;
dr.setCommandListener(this);
Display.getDisplay(this).setCurrent(dr);
}
public void pauseApp() {}
public void destroyApp(boolean unconditional)}
public void сommandAction(Command c, Displayable d)
{
if (c == exitMidlet)
{
destroyApp(false); notifyDestroyed();
}
}
}
/**
класс Draw определен в файле Draw.Java рисует круг и квадрат
*/
import javax.microedition.Icdui.*;
public class Draw extends Canvas implements Runnable
{
// позиция для перемещения квадрата и круга
int position = 0;
// узнаем ширину экрана
int endX = getWidth();
// конструктор
public Draw()
{ super();
}
public void start()
{
// создаем и запускаем поток
Thread t = new Thread(this);
t.start();
}
//метод run интерфейса Runnable public void run()
{
// бесконечный цикл
while (true)
{
// сравниваем позицию if(position > endX)
{
//уменьшаем позицию position = endX;
}
// увеличиваем позицию на 1 position ++;
// обновляем экран repaint();
// останавливаем цикл на 20 миллисекунд
try { Thread.sleep(20); }
catch (java.lang.InterruptedException zxz) {}
}
}
publie void paint(Graphics g)
{
// вычисляем область для перерисовки экрана
int х = g.getClipWidth() ;
int у = g.getClipHeight () ;
// устанавливаем желтый цвет фона
g.setColor(0xffff00);
// назначаем перерисовку всему экрану
g.fillRect(0,0,x,y);
// устанавливается синий цвет квадрата
g.setColor(0, 0, 200);
// рисуем квадрат
g.fillRect(position, 40, 20, 20);
// устанавливается красный цвет круга
g.setColor(250, 0,0);
// рисуем круг
fillArc(position, 10, 20, 20, 45, 360);
}
}
В файле Main.java создается объект класса Draw, запускается системный поток и отражается текущий экран. Файл Draw.java содержит полную реализацию класса Draw. Начальные позиции квадрата и круга определяются переменной position, которой задано нулевое значение. Для того чтобы определить момент столкновения, необходимо знать точку или координаты препятствия. В этом примере как препятствие применяется конец экрана с правой стороны, поэтому с помощью метода getWidth () находится конец экрана.
int endX = getWidth();
В последствии значение переменной endx будет использовано для определения столкновения.
Конструктор, класса Draw обращается к конструктору своего суперкласса Canvas. В методе run() и цикле while с помощью оператора отношения if происходит сравнение текущей позиции двух объектов и конца экрана.
if (position > endX)
{
position = endX -;
}
После того как позиция обоих объектов становится больше значения переменной endx, то есть квадрат и круг достигают конца экрана, то происходит уменьшение переменной position. Это приводит к движению объектов в обратном направлении.
После компиляции примера на экране появятся два движущихся слева направо объекта. Мы использовали препятствие в виде одной стороны экрана, но точно таким же образом можно определить столкновение для всех сторон экрана или просто для статических объектов. Выполните эти упражнения в качестве домашнего задания, а также внимательно посмотрите на процесс работы примера из листинга 7.7, квадрат и круг все-таки не отталкиваются от кромки экрана, а исчезают. Это происходит, потому что опорная позиция объектов находится в левом верхнем углу и столкновение происходит именно с позицией объектов* Вы знаете размер круга и квадрата, подумайте как можно определить более точную точку столкновения объектов и кромки экрана.
Перемещение объекта с помощью клавиш
Перемещение объекта по экрану телефона с помощью клавиш телефона, это, пожалуй, самое главное действие в играх. Для передвижения нужно воспользоваться методом key Pressed () и описать код производящий обработку событий получаемых с клавиш телефона. При реализации метода keyPressed (), сначала необходимо вызвать метод getGameAction () для получения ключевого код»! с последующей его обработкой. Для обработки полученного ключевого кода можно применить оператор switch и тогда метод keyPressed () может принять следующий вид:
protected void keyPressed(int keyCode)
{
// получаем игровые события
int act = getGameAction{keyCode);
// обработка событий
switch(act)
{
case Canvas.LEFT:
// движение влево break; case Canvas .RIGHT:
// движение вправо break;
case Canvas.UP:
// движение вверх
break;
case Canvas.DOWN:
// движение вниз break; default: break;
}
}
}
Код обработки для клавиш зависит от того, что именно вы желаете сделать с объектом. Давайте возьмем за основу код из листинга 7.5, где был нарисован квадрат, и модернизируем его. Выведем синий квадрат на экран, для этого создадим две переменные positionX и positionY, отвечающие за точку вывода квадрата на экран и присвоим им значения близкие к центру экрана.
int positionX. = getWidth()/2;
int positionY = getHeight()/2;
При нажатии клавиш можно воспользоваться значениями переменных positionX и positionY для перемещения квадрата по экрану, увеличивая или уменьшая значения этих двух переменных. Значение, на которое вы будете увеличивать или уменьшать переменные, фактически будет обозначать скорость перемещения квадрата по экрану. В листинге 7.8 дается полный код программы перемещения элемента по экрану с помощью клавиш.
/** Листинг 7.8
Класс Main и класс Draw
*/
import javax.microedition.Icdui.*;
import javax.microedition.midlet.*;
public class Main
extends MIDlet implements CommandListener .
{
// команда выхода из программы
private Command
exitMidlet = new Command(«Выход», Command.EXIT, 0);
public void startApp()
{
// создаем объект класса Draw
Draw dr = new Draw();
// запускаем поток
dr.start();
// добавляем команду выхода
dr.addCommand(exitMidlet);
dr.setCommandListener(this);
Display.getDisplay(this).setCurrent(dr);
}
public void pauseApp() {}
public void destroyApp(boolean unconditional){}
public void сommandAction(Command c, Displayable d)
{
if (c = = exitMidlet)
{
destroyApp(false); notifyDestroyed()
}
}
}
/**
класс Draw определен в файле Draw.Java
передвижение квадрата с помощью клавиш телефона
*/
import javax.microedition.Icdui.*;
public class Draw extends Canvas implements Runnable
{
// устанавливаем квадрат в центр экрана
int positionX = getwidth()/2;
// устанавливаем квадрат в центр экрана
int positionY = getHeight()/2;
// конструктор
public Draw()
{ super(); }
public void start()
{
// создаем и запускаем поток
Thread t = new Thread(this);
t.start();
}
// метод run интерфейса Runnable public void run()
{
// бесконечный цикл
while (true)
{
// обновляем экран
repaint();
// останавливаем цикл на 20 миллисекунд
try { Thread, sleep (20) ,- }
catch (Java.lang.InterruptedException zxz) {}
}
}
public void paint(Graphics g)
{
// вычисляем область для перерисовки экрана
int x = g.getClipWidth();
int у = g.getClipHeight();
// устанавливаем белый цвет фона
g.setColor(0xffffff);
// назначаем перерисовку всему экрану
g.fillRect(0,0,х,у);
// устанавливается зеленный цвет квадрату
g.setColor(0, 0, 200);
7/ рисуем квадрат
g.fillRect(positionX, positionY, 20, 20);
projected void keyPressed(int keyCode)
// скорость передвижения
int speed = 3 ;
// получаем игровые события
int act = getGameAction(keyCode);
// обработка событий
switch(act)
{
// движение влево case Canvas.LEFT:
positionX -= speed;
break;
// движение вправо case Canvas.RIGHT:
positionX += speed;
break;
// движение вверх case Canvas.UP:
positionY -= speed;
break;
// движение вниз case Canvas.DOWN:
positionY += speed;
break; default: break;
}
}
}
В следующей главе рассматриваются игровые классы, доступные в профиле MIDP 2.0 и значительно упрощающие создание игрового цикла, а также игровом графики.