У цій статті


Пакет MIDP 2.0 містить Game API, що спрощує створення двовимірних ігор. Цей API
є компактним і складається всього з п'яти класів у пакеті
javax.microedition.lcdui.game. Ці класи забезпечують дві основні
можливості:



Побудова циклу гри за допомогою GameCanvas


GameCanvas являє собою клас Canvas з
додатковими можливостями; він надає методи для безпосередньої
промальовування, а також для контролю за станом клавіатури. Ці нові методи
роблять можливим укладення всієї функціональності гри в одному циклі під
керуванням одного потоку. Для того щоб побачити переваги цього підходу,
подумайте, як би ви могли реалізувати типову гру з використанням
Canvas:

	public void MicroTankCanvas
extends Canvas
implements Runnable {
public void run() {
while (true) {
/ / Поновити стан гри.
repaint();
/ / Затримка на один крок часу.
}
}

public void paint(Graphics g) {
/ / Код промальовування.
}

protected void keyPressed(int keyCode) {
/ / Реакція на натискання клавіш.
}
}


Це не зовсім вдала схема. Метод run(), Що виконується в потоці
додатки, оновлює гру в кожному циклі. Типовою завданням може бути
оновлення позиції м'яча або зорельота і анімація персонажа або об'єкту. Для
оновлення екрану метод repaint() викликається в кожному циклі.
Система передає події від клавіатури в метод keyPresed(), Який
відповідним чином оновлює стан гри.


Проблема полягає в тому, що все працює в різних потоках і код гри
розподіляється по трьом різним методам. При виклику в основному циклі анімації в
main() методу repaint() немає способу точно визначити
час виклику системою методу paint(). При виклику системою методу
keyPressed() немає способу дізнатися, що відбувається в інших частинах
додатки. Якщо ваша програма в keyPressed() виробляє
оновлення стану гри одночасно з промальовуванням екрану в
paint(), Екран може виглядати не так, як передбачалося. Якщо
промальовування екрану виконується довше часу одного циклу програми в
run(), То анімація може виглядати переривчастої або незвичайною.


GameCanvas дає вам можливість обійти звичайні механізми
промальовування і обробки подій клавіатури, і вся логіка гри може бути
зосереджена в одному циклі. По-перше, GameCanvas дозволяє вам
звернутися безпосередньо до свого об'єкту Graphics за допомогою
методу getGraphics(). Вся промальовування на отриманому об'єкті
Graphics виконується в неекранном буфері. Ви можете потім
скопіювати буфер на екран, використовуючи flushGraphics(), Який не
повертає управління до тих пір, поки екран не буде оновлено. Такий підхід
забезпечує більш тонкий контроль, ніж виклик repaint(). Метод
repaint() відразу повертає управління, і ваш додаток не може
точно визначити, коли система викличе paint() для оновлення
екрану.


GameCanvas також містить метод для отримання поточного стану
клавіатури, який можна застосувати для так званого опитування. Замість очікування
виклику системою keyPressed() ви можете безпосередньо визначити,
які клавіші натискати, викликавши метод GameCanvas
getKeyStates().


Типовий цикл ігри з використанням GameCanvas може виглядати
приблизно так:

	public void MicroTankCanvas
extends GameCanvas
implements Runnable {
public void run() {
Graphics g = getGraphics();
while (true) {
/ / Поновити стан гри.
int keyState = getKeyStates();
/ / Реакція на натискання клавіш.
/ / Код промальовування.
flushGraphics();
/ / Затримка на один крок часу.
}
}
}

У наступному прикладі демонструється основний цикл ігрової програми. Вона
показує обертається символ X, Який ви можете переміщати по
екрану за допомогою клавіш переміщення курсора. Метод run() є
надзвичайно ясним, завдяки об'єкту GameCanvas.

	import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;

public class SimpleGameCanvas
extends GameCanvas
implements Runnable {
private boolean mTrucking;
private long mFrameDelay;

private int mX, mY;
private int mState;

public SimpleGameCanvas() {
super(true);
mX = getWidth() / 2;
mY = getHeight() / 2;
mState = 0;
mFrameDelay = 20;
}

public void start() {
mTrucking = true;
Thread t = new Thread(this);
t.start();
}

public void stop() { mTrucking = false; }

public void run() {
Graphics g = getGraphics();

while (mTrucking == true) {
tick();
input();
render(g);
try { Thread.sleep(mFrameDelay); }
catch (InterruptedException ie) {}
}
}

private void tick() {
mState = (mState + 1) % 20;
}

private void input() {
int keyStates = getKeyStates();
if ((keyStates & LEFT_PRESSED) != 0)
mX = Math.max(0, mX – 1);
if ((keyStates & RIGHT_PRESSED) != 0)
mX = Math.min(getWidth(), mX + 1);
if ((keyStates & UP_PRESSED) != 0)
mY = Math.max(0, mY – 1);
if ((keyStates & DOWN_PRESSED) != 0)
mY = Math.min(getHeight(), mY + 1);
}

private void render(Graphics g) {
g.setColor(0xffffff);
g.fillRect(0, 0, getWidth(), getHeight());

g.setColor(0x0000ff);
g.drawLine(mX, mY, mX – 10 + mState, mY – 10);
g.drawLine(mX, mY, mX + 10, mY – 10 + mState);
g.drawLine(mX, mY, mX + 10 – mState, mY + 10);
g.drawLine(mX, mY, mX – 10, mY + 10 – mState);

flushGraphics();
}
}


Вихідний код прикладу в даній статті містить мідлет, який використовує цю
канву. Спробуйте запустити SimpleGameMIDlet для того, щоб
подивитися, як все це працює. Ви побачите щось схоже на морську зірку,
виконує вільні вправи (можливо, компенсуючи відсутність ніг).



Образ екрану
SimpleGameMIDlet

Ігрові сцени схожі на цибулину


Типова двовимірна гра складається з фонового зображення і декількох
анімованих персонажів. І хоча ви можете намалювати подібну сцену
самостійно, Game API надає вам можливість побудови сцен при
допомоги рівнів. Ви можете створити на одному рівні фон міста, а на іншому
рівні – автомобіль. Приміщення рівня з автомобілем поверх рівня з фоном
формує повну сцену. Використання автомобіля на окремому рівні полегшує
маніпулювання ним незалежно від фону і від будь-яких інших рівнів сцени.


Game API забезпечує гнучку підтримку рівнів за допомогою чотирьох
класів:



Використання TiledLayer


Клас TiledLayer є простим для розуміння і в той же час
має деякі не видимі на перший погляд внутрішні нюанси. Основна його ідея
– Вихідне зображення надає набір елементів, які можуть бути
розміщені при формування великої сцени. Наприклад, наступне зображення має
розмір 64 х 48 пікселів.



Початкове зображення

Це зображення може бути розділене на 12 елементів розміром 16 х 16
пікселів. Клас TiledLayer присвоює кожному елементу зображення
номер, починаючи з 1 у верхньому лівому кутку. Елементи у вихідному зображенні
пронумеровані таким чином:



Нумерація елементів
зображення

Цього достатньо для створення TiledLayer у вихідному коді
програми. Ви повинні вказати кількість стовпців і рядків, вихідне зображення і
розмір елементів вихідного зображення в пікселях. Наступний фрагмент
демонструє процес завантаження і створення TiledLayer.

	Image image = Image.createImage("/board.png");
TiledLayer tiledLayer = new TiledLayer(10, 10, image, 16, 16);

У даному прикладі новий об'єкт TiledLayer має 10 стовпців і 10
рядків. Взяті з зображення елементи являють собою квадрати розміром 16
пікселів.


Створення сцени за допомогою цих елементів є простим завданням. Для
присвоєння елемента будь-якої осередку викличте setCall(). Ви повинні
вказати номер стовпця і рядка клітинки і номер елемента. Наприклад, ви можете
присвоїти елемент 5 частини третьої осередку у другому рядку, викликавши метод
setCell(2, 1, 5). Ці параметри можуть здатися вам не
правильними. Але зверніть увагу, що індекс елемента починається з 1, а номери
стовпця і рядка починаються з 0. За замовчуванням всі клітинки нового об'єкта
TiledLayer мають значення номера елемента дорівнює 0. Це означає, що
вони порожні.


Наступний фрагмент демонструє один зі способів заповнення
TiledLayer за допомогою масиву чисел, що мають тип
integer. У реальній грі TiledLayers може
визначатися з файлів ресурсів, що забезпечить більшу гнучкість у побудові
фону і розвитку гри за рахунок нових ігрових полів або рівнів.

	private TiledLayer createBoard() {
Image image = null;
try { image = Image.createImage("/board.png"); }
catch (IOException ioe) { return null; }

TiledLayer tiledLayer = new TiledLayer (10, 10, image, 16, 16);

int[] map = {
1, 1, 1, 1, 11, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 9, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 7, 1, 0, 0, 0, 0, 0,
1, 1, 1, 1, 6, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 7, 11, 0,
0, 0, 0, 0, 0, 0, 7, 6, 0, 0,
0, 0, 0, 0, 0, 7, 6, 0, 0, 0
};

for (int i = 0; i < map.length; i++) {
int column = i % 10;
int row = (i – column) / 10;
tiledLayer.setCell(column, row, map[i]);
}

return tiledLayer;
}


Для відображення об'єкта TiledLayer на екрані необхідно передати
об'єкт Graphics в його метод paint().


TiledLayer підтримує також анімовані елементи, що спрощують
переміщення набору осередків по послідовності елементів. Більш детальна
інформація наведена в документації по API для TiledLayer.


Використання спрайтів для анімації персонажа


Іншим конкретним об'єктом Layer, Що надаються в Game API,
є Sprite. У певному сенсі, Sprite є
концептуальної інверсією TileLayer. У той час як
TiledLayer використовує палітру елементів вихідного зображення для
формування великої сцени, Sprite використовує послідовність
фреймів вихідного зображення для анімації.


Все, що вам необхідно для створення об'єкту Sprite – Вихідне
зображення і розмір кожного фрейму. У TiledLayer вихідне
зображення розділяється на однакові за розміром елементи; в Sprite
елементи зображення називаються фреймами. У наведеному нижче прикладі для
створення об'єкту Sprite з розміром фрейму 32 х 32 пікселя
використовується вихідне зображення tank.png.

	private MicroTankSprite createTank() {
Image image = null;
try { image = Image.createImage("/tank.png"); }
catch (IOException ioe) { return null; }

return new MicroTankSprite(image, 32, 32);
}


Кожен кадр вихідного зображення має номер, що починається з нуля і вище.
(Не плутайтеся! Пам'ятайте, що номери елементів зображення починаються з 1.) Об'єкт
Sprite містить послідовність фреймів, Визначальну
порядок відображення фреймів. За замовчуванням послідовність фреймів у новому
об'єкті Sprite починається з 0 і продовжується далі по всім доступним
фреймах.


Для переходу до наступного або попереднього фрейму в послідовності фреймів
використовуються методи Sprite nextFrame() і prevFrame().
Ці методи виконують циклічні переходи на початку або кінці послідовності
фреймів. Наприклад, якщо об'єкт Sprite показує останній кадр в
послідовності фреймів, виклик методу nextFrame() викличе показ
першого фрейму послідовності.


Для вказівки відмінною від прийнятої за замовчуванням послідовності фреймів,
передайте послідовність у вигляді масиву цілих чисел в метод
setFrameSequence().


Є можливість перейти до певного елемента поточної послідовності
фреймів за допомогою виклику методу setFrame(). Ні способу перейти до
конкретного фрейму за його номером. Ви можете тільки перейти до певного
елементу в послідовності фреймів.


Зміни фрейму стануть видимими тільки при наступній промальовуванні об'єкта
Sprite за допомогою методу paint(), Успадкованих від
Layer.


Об'єкт Sprite може також трансформувати вихідні фрейми. Їх
можна обертати на кути, кратні 90 градусів, дзеркально відображати, або
комбінувати ці операції. Для цього застосовуються константи класу
Sprite. Поточна трансформація об'єкта Sprite може
бути встановлена шляхом передачі однієї з цих констант в метод
setTransform(). У наступному прикладі поточний фрейм дзеркально
відображається навколо вертикальної осі і повертається на 90 градусів:

	// Sprite sprite = …
sprite.setTransform(Sprite.TRANS_MIRROR_ROT90);

Трансформації діють так, щоб опорний піксель об'єкта
Sprite не переміщався. За замовчуванням опорний піксель розташований за
координатах 0,0 в координатній сітці об'єкта Sprite в
його верхньому лівому кутку. При виконанні трансформації розташування опорного
пікселя також трансформується. Місце розташування об'єкта Sprite
коректується так, щоб опорний піксель залишився на тому ж самому місці.


Ви можете змінити місце розташування опорного пікселя за допомогою методу
defineReferencePixel(). Для багатьох видів анімації ви будете
визначати опорний піксель в центрі спрайту.


І, нарешті, клас Sprite надає кілька методів
collidesWith() для виявлення конфліктів з іншими об'єктами
Sprite, TiledLayer або Image. Ви можете
знайти конфлікт за допомогою конфліктних прямокутників (швидко, але грубо)
або на рівні пікселів (повільно, але точно). Нюанси використання цих методів
наведені в документації по API.


Приклад muTank


Приклад muTank
демонструє використання об'єктів TiledLayer,
Sprite і LayerManager.



Приклад muTank

Найважливішими класами є MicroTankCanvas, Що містить велику
частина коду, і MicroTankSprite, Який інкапсулює поведінку
танка.


MicroTankSprite інтенсивно застосовує трансформації. Використовуючи
вихідне зображення тільки з трьома фреймами, MicroTankSprite може
відобразити танк, який вказує на 16 різних напрямів. Два відкритих
public методу turn() і forward()
полегшують керування танком.


MicroTankCanvas являє собою підклас
GameCanvas і містить цикл анімації у методі run(),
який повинен бути вам знаком. Метод tick() визначає факт
зіткнення танка з кордоном поля. Якщо це виявлено, останнє переміщення
скасовується за допомогою методу MicroTankSprite undo().
Метод input() просто контролює натиснення клавіш і відповідним
чином коригує напрямок або позицію танка. Метод render()
використовує об'єкт LayerManager для управління промальовуванням. Об'єкт
LayerManager містить два рівні – один для танка, один для
ігрового поля.


Метод debug(), Що викликається з циклу програми, порівнює час
циклу програми з бажаним часом циклу (80 мілісекунд) і відображає відсоток
використаного часу на екрані. Цей процес виконується тільки в
діагностичних цілях і повинен бути вилучений перед передачею ігри
користувачам.


Синхронізація циклу гри є більш складною, ніж у попередньому прикладі
SimpleGameCanvas. Для того щоб виконати одну ітерацію циклу гри
точно за 80 мілісекунд, програма MicroTankCanvas вимірює час,
витрачений на виконання tick(), input() і
render(). Потім вона зупиняється на що залишився до 80
мілісекунд час циклу, підтримуючи загальний час виконання кожної ітерації як
якомога ближче до значення в 80 мілісекунд.


Резюме


Game API пакету MIDP 2.0 надає середу, спрощує розробку двовимірних
ігор. По-перше, клас GameCanvas забезпечує методи промальовування і
введення подій клавіатури, які роблять можливим створення компактного циклу
ігрової програми. Далі, система рівнів дає можливість створювати складні
сцени. Клас TiledLayer збирає великий фон або сцену з палітри
елементів вихідного зображення. Клас Sprite підходить для
анімованих персонажів і здатний виявляти конфлікти з іншими об'єктами в
грі. Клас LayerManager є елементом, що збирає
разом різні рівні. Приклад muTank надає основу
робочого коду, що демонструє використання Game API.


Про автора: Jonathan Knudsen [e-mail] [домашня сторінка] є автором
кількох книг, у тому числі "Бездротова Java,
друге видання "," Неофіційне керівництво по
роботам LEGO MINDSTORMS "," Вивчення Java, друге
видання "і" 2D-графіка в
Java ". Джонатан написав багато статей про Java і роботах Lego, в тому числі
статті для JavaWorld, EXE, NZZ Folio і O "Reilly Network. Джонатан має ступінь
Прінстонського університету з машинобудування.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*