Програма розроблялася в інтегрованому сереловищі NetBeans IDE 7.0. [6]
В одержаній реалізації, гра матиме такий вигляд.
Скріншот зроблено у програмі MicroEmulator 2.0.4.62.
Дана версія розрахована на дисплеї з роздільною
здатністю 640х360 пікселів. Програма тестувалась на телефоні Sony Ericsson U1,
де успішно працювала.
В ході виконання роботи було запропоновано реалізацію гри, для мобільних пристроїв з сенсорним екраном на базі обраної платформи Java Micro Edition. Отримано досвід роботи з інструментами розробки програм для мобільних пристроїв, зокрема в середовищі NetBeans IDE 7.0.
Важливими особливостями такого програмування є розміри графічних елементів інтерфейсу - адже це значним чином впливає на зручність користування продуктом. При цьому потрібно враховувати роздільну здатність екрану і точність сенсору, а також користуватиметься людина стилусом, чи пальцем. Можливий варіант виходу з ситуації - розробка різних версій продукту для пристроїв з кардинально різними характеристиками. Завдяки універсальності платформи Java ME цей процес зводиться до заміни графічних файлів і констант, а не повного переписування коду.
Варто зазначити, що розробка ігрових програм для
портативних пристроїв - доволі перспективна сфера, адже кількість мобільних
телефонів з сенсорним екраном постійно зростає, а людству ніколи не набридне
грати в цікаві ігри, що зумовлює попит на відповідне програмне забезпечення.
1. Горнаков С.Г. "Програмирование мобильних телефонов на Java 2 Micro Edition" - М.: ДМК Пресс, 2004
2. David Fox, Roman Verhosek "Micro Java Game Development" - Addison Wesley, 2002
3. MIDP Programming with J2ME: http://www.developer.com/java/j2me/article. php/10934_1561591_8/MIDP-Programming-with-J2ME. htm <http://www.developer.com/java/j2me/article.php/10934_1561591_8/MIDP-Programming-with-J2ME.htm>
. Мухин И.А. Сенсорные экраны - решение проблем (10 технологий). "BROADCASTING Телевидение и радиовещение": 1 часть - № 3 (55) май 2006, с.50-52; 2 часть - № 4 (56) июнь-июль 2006, с.40-41; 3 часть - № 7 (59) ноябрь 2006, с.64-66.
. Офіційний сайт Java Platform, Micro Edition: <http://www.oracle.com/technetwork/java/javame/index.html>
. Офіційний
сайт середовища розробки NetBeans IDE: <http://netbeans.org/>
Додаток: Код програми
// GameField. java
import java. io. IOException;javax. microedition. lcdui. Graphics;javax. microedition. lcdui. Image;javax. microedition. lcdui. game. TiledLayer;class GameField {final int GRASS = 1;final int ROAD = 2;final int HOME = 3;[] [] field;tl;GameField (int [] [] field) {. field = field;(field. length, field [0]. length,100,0);
}
// генерує поле по замовчуваннюGameField () {n = 10;m = 15;= new int [n] [m];(int i=0; i<n; i++)(int j=0; j<m; j++)[i] [j] =GRASS;[0] [0] = HOME;[0] [1] = ROAD;[0] [2] = ROAD;[1] [2] = ROAD;[2] [2] = ROAD;[3] [2] = ROAD;[4] [2] = ROAD;[4] [3] = ROAD;[1] [3] = ROAD;[1] [4] = ROAD;[2] [4] = ROAD;[3] [4] = ROAD;[4] [4] = ROAD;[4] [5] = ROAD;[4] [6] = ROAD;[4] [7] = ROAD;[5] [7] = ROAD;[6] [7] = ROAD;[7] [7] = ROAD;[8] [7] = ROAD;[8] [8] = ROAD;[8] [9] = ROAD;[8] [10] = ROAD;[8] [11] = ROAD;[8] [12] = ROAD;[8] [13] = ROAD;[9] [13] = ROAD;[9] [14] = ROAD;(n, m,100,0);
}void createLayer (int n, int m, int x0, int y0) {{= new TiledLayer (m, n, Image. createImage ("/field. png"), 36, 36);
} catch (IOException ex) {. printStackTrace ();
}. setPosition (x0, y0);(int i=0; i<n; i++)(int j=0; j<m; j++). setCell (j, i, field [i] [j]);
}void paint (Graphics g) {. paint (g);
}point getCellij (int x, int y) {- = tl. getX ();- = tl. getY ();new point (x / tl. getCellWidth (),y /tl. getCellHeight ());
}TrekBuilder genTrekBuilder () {tb = new TrekBuilder (36,36,tl. getX (),tl. getY ());. buildTreks (field, 9, 14);tb;
}point getCellXY (point p) {new point (p. x*tl. getCellWidth () +tl. getX (), p. y*tl. getCellHeight () +tl. getY ());
}boolean isCellAvailable (int i, int j) {( (j >=0 && j<field. length && i>=0 && i<field [j]. length) && field [j] [i] == GRASS);
}void checkCell (point p) {= getCellij (p. x, p. y);s = (field [p. x] [p. y] == GRASS)?"Grass": "";= (field [p. x] [p. y] == ROAD)?"road": s;= (field [p. x] [p. y] == HOME)?"home": s;. out. println (s + " ("+p. x + " " + p. y + ")");
}
}
// Menu. java
import java. io. IOException;javax. microedition. lcdui. Graphics;javax. microedition. lcdui. Image;class Menu {bgimg,changeimg,sellimg;pc;sell = false;Menu (TDCanvas pc) {. pc = pc;{= Image. createImage ("/menu. png");= Image. createImage ("/change. png");= Image. createImage ("/sell. png");
} catch (IOException ex) {. printStackTrace ();
}
}void changeState (boolean sell) {. sell = sell;
}void paint (Graphics g) {. drawImage (bgimg, 0, 0, Graphics. TOP|Graphics. LEFT);(sell) g. drawImage (sellimg, 7, 188, Graphics. TOP|Graphics. LEFT);g. drawImage (changeimg, 7, 188, Graphics. TOP|Graphics. LEFT);
}void pressed (int x, int y) {(x>7 && x<83 && y>188 && y<264) {(sell) {. removeCur ();= false;
}pc. changeCurTower ();
}
}
}
// MenuCanvas. java
import javax. microedition. lcdui. Canvas;javax. microedition. lcdui. Display;javax. microedition. lcdui. Graphics;class MenuCanvas extends Canvas{cenX = 0;cenY = 0;selected;[] options = {"Campaign","Survival","Exit"};tdg;MenuCanvas (TDGame tdg) {();. tdg = tdg;(true);= getWidth () /2;= getHeight () /2;
}void pointerPressed (int x, int y) {(x > cenX)(y >cenY) select ();cancel ();(y >cenY) down ();up ();
}void paint (Graphics g) {. setGrayScale (255);. setColor (10, 200, 100);. fillRect (0, 0, getWidth (), getHeight ());. setGrayScale (0);(int i=0; i<options. length; i++) {(i == selected) g. setColor (200, 20,10);. drawString (options [i], cenX - cenX/2, i*15,Graphics. TOP|Graphics. LEFT);. setColor (0,0,0);
}. drawString ("ok", 5+ cenX + cenX/2, 5+ cenY + cenY/2,Graphics. TOP|Graphics. LEFT);. drawString ("cancel", 5+ cenX + cenX/2,cenY/2 - 5,Graphics. TOP|Graphics. LEFT);. drawString ("up", 2, cenY/2 - 5, Graphics. TOP|Graphics. LEFT);. drawString ("down", 2, 5+ cenY + cenY/2, Graphics. TOP|Graphics. LEFT);
}void up () {= (selected>0)? selected-1: options. length-1;();
}void down () {= (selected<options. length-1)? selected+1: 0;();
}void select () {(selected) {0:. getDisplay (tdg). setCurrent (new TDCanvas (tdg, TDCanvas. CAMPAIGN));;1:. getDisplay (tdg). setCurrent (new TDCanvas (tdg, TDCanvas. SURVIVAL));;2:. destroyApp (false);. notifyDestroyed ();;
}
}void cancel () {(tdg. canContinue ()) tdg. continueGame ();
}
}
// Monster. java
import javax. microedition. lcdui. Graphics;javax. microedition. lcdui. Image;javax. microedition. lcdui. game. Sprite;class Monster {sp;speed;maxspeed;minspeed = 1;hp;dmg;gold;tr;Monster (int speed, int hp, int dmg, int gold, Image img) {. speed = speed;. hp = hp;. dmg = dmg;. gold = gold;= new Sprite (img,36,36);
}void SetXY (int x, int y) {. setPosition (x, y);
}void SetXY (point p) {. setPosition (p. x, p. y);
}void paint (Graphics g) {. paint (g);(sp. getFrame ()! = 0) sp. setFrame (0);sp. setFrame (1);
}void setTrek (Trek tr) {. tr = tr;(tr. getNext ());
}boolean move () {();res = moveWith (speed);res;
}boolean moveWith (int step) {(tr. isEmty ()) {
// видалити монстра= 0;false;
}{pto = tr. getNext ();cur = new point (sp. getX (),sp. getY ());(cur. equals (pto)) {. delNext ();();
}{step1 = 0;(pto. x > cur. x) {= changeStep (cur. x,pto. x,step);. x+=step1;= step1;
}(pto. x < cur. x) {= changeStep (cur. x,pto. x,step);. x-=step;= step1;
}(pto. y > cur. y) {= changeStep (cur. y,pto. y,step);. y+=step;= step1;
}(pto. y < cur. y) {= changeStep (cur. y,pto. y,step);. y-=step;= step1;
}(cur);(step! =0) moveWith (step);
}true;
}
}int changeStep (int f, int t, int step) {dist = Math. abs (f-t);(dist<step) return dist;return step;
}point getCentPosition () {new point (sp. getX () +sp. getWidth () /2,sp. getY () +sp. getHeight () /2);
}point getPosition () {new point (sp. getX (),sp. getY ());
}void hit (int d) {- = d;(! isAlive ()) {. st. removeAllElements ();
}. setFrame (2);
}void hitSpeed (int d) {- = d;(speed < minspeed) speed = minspeed;
}void Speed () {(speed < maxspeed) speed += 1;
}boolean isAlive () {(hp>0);
}int getHp () {hp;
}void setHp (int hp) {. hp = hp;
}
}
// MonsterFactory. java
public interface MonsterFactory {Monster genNext ();
}
// Monsters. java
import java. util. Vector;javax. microedition. lcdui. Graphics;class Monsters {mar = new Vector ();pc;Monsters (TDCanvas pc) {. pc = pc;
}void add (Monster m) {. addElement (m);
}void move () {(int i=0; i<mar. size (); i++) {m = ( (Monster) mar. elementAt (i));(m. isAlive ()) m. move ();{. removeElementAt (i);p = m. getPosition ();(p. x == 100 && p. y == 0) pc. hp - = m. dmg;pc. gold += m. gold;
}
}
}Monster [] getArray () {[] oar = new Monster [mar. size ()];. copyInto (oar);oar;
}void paint (Graphics g) {(int i=0; i<mar. size (); i++) {
( (Monster) mar. elementAt (i)). paint (g);
}
}
}
// SimpleFactory. java
import java. util. Random;javax. microedition. lcdui. Image;class SimpleFactory implements MonsterFactory{tb;img;start;SimpleFactory (TrekBuilder tb, Image img) {. tb = tb;. img = img;
}Monster genNext () {r = new Random ();speed = r. nextInt (8) +2;m = new Monster (speed, 30, 7, 3, img);. setTrek (tb. randTrek ());m;
}
}
// SimpleTower. java
import javax. microedition. lcdui. Image;class SimpleTower extends Tower{SimpleTower (int xp, int dmg, int range, int gold, int cooldown, Image img) {(xp, dmg,range,gold, cooldown, img,"damages\n one");
}boolean attack (Monster [] m) {. setFrame (0);(int i=0; i<m. length; i++) {(isInRange (m [i])) {[i]. hit (dmg);. setFrame (1);true;
}false;
}void upgrade () {(xp % 20 == 0) dmg ++;(xp % 30 == 0) range ++;
}
}
// SpeedTower. java
import javax. microedition. lcdui. Image;class SpeedTower extends Tower{SpeedTower (int xp, int dmg, int range, int gold, int cooldown, Image img) {(xp, dmg,range,gold, cooldown, img,"damages\n speed");
}boolean attack (Monster [] m) {. setFrame (0);(int i=0; i<m. length; i++) {(isInRange (m [i])) {[i]. hitSpeed (dmg);. setFrame (1);true;
}
}false;
}void upgrade () {(xp % 20 == 0 && cooldown>0) cooldown--;(xp % 30 == 0) range++;
}
}
// SplashTower. java
import javax. microedition. lcdui. Image;class SplashTower extends Tower{SplashTower (int xp, int dmg, int range, int gold, int cooldown, Image img) {(xp, dmg,range,gold, cooldown, img,"splash\n damage");
}boolean attack (Monster [] m) {hit = false;. setFrame (0);(int i=0; i<m. length; i++) {(isInRange (m [i])) {[i]. hit (dmg);. setFrame (1);= true;
}
}hit;
}void upgrade () {(xp % 20 == 0 && cooldown>0) cooldown--;(xp % 30 == 0) dmg++;
}
}
// TDGame. java
import java. util. Random;javax. microedition. midlet. *;javax. microedition. lcdui. *;javax. microedition. lcdui. game. GameCanvas;class TDGame extends MIDlet
{Display display;void startApp () {= Display. getDisplay (this);. setCurrent (new MenuCanvas (this));
}void pauseApp () {}void destroyApp (boolean forced) {}TDCanvas PausedGame = null;void setToPause (TDCanvas tdc) {= tdc;
}boolean canContinue () {(PausedGame! = null);
}void continueGame () {. getDisplay (this). setCurrent (PausedGame);. continueGame ();= null;
}
}TDCanvas extends GameCanvas implements Runnable{img, im1, im2, im3,mimg;t;gf = new GameField ();tb;tws;mf;ms;static int hp = 100;pe;m = new Menu (this);startTime = System. currentTimeMillis ();gold = 100;static final int SURVIVAL = 0;static final int CAMPAIGN = 1;TDCanvas (TDGame pe, int mode) {(true);(true);. pe = pe;{= Image. createImage ("/monstr_fastt. png");= Image. createImage ("/tower_simple. png");= Image. createImage ("/tower_speed. png");= Image. createImage ("/tower_splash. png");
} catch (Exception ex) {. out. println (ex. getMessage ());
}= gf. genTrekBuilder ();= new SimpleFactory (tb, img);= new Towers ();= new Monsters (this);. add (mf. genNext ());= new Thread (this);. start ();
}curTower = - 1;void removeCur () {+= tws. remove (curTower);
}void pointerPressed (int x, int y) {(x>100) {= tws. check (new point (x,y));(curTower == - 1) {. moveNew (x, y);. changeState (false);. changeState (false);
}{. changeState (true);. changeState (true);
}
}{(y<90) {= true;. setToPause (this);. getDisplay (pe). setCurrent (new MenuCanvas (pe));
}. pressed (x, y);
}
}simple = 0;void changeCurTower () {++;(simple > 2) simple =0;(simple);
}void setCurTower (int simple) {(simple == 0) tws. tryNew (new SimpleTower (0, 7, 36, 10, 5, im1));if (simple == 1) tws. tryNew (new SpeedTower (0, 2, 36, 20, 5, im2));tws. tryNew (new SplashTower (0, 4, 36, 15, 10, im3));
}void pointerReleased (int x, int y) {(! m. isSell ()) {res = tws. put (gf,gold);(res! =-1) {= res;(simple);
}
}
}void pointerDragged (int x, int y) {. moveNew (x, y);
}void painting (Graphics g) {. setGrayScale (255);. fillRect (0, 0, getWidth (), getHeight ());. setGrayScale (0);. drawRect (0, 0, getWidth (), getHeight ());. paint (g);. paint (g);. paint (g);. paint (g);(m. isSell ()) tws. paintInfo (g, 7,101, curTower);c = g. getColor ();. setColor (0, 230, 25);. drawString (" hp: "+ hp, 6, 10,Graphics. TOP|Graphics. LEFT);. setColor (255, 245, 48);. drawString ("Gold: "+ gold, 6, 25,Graphics. TOP|Graphics. LEFT);. setColor (255, 10,10);. drawString ("Mons: "+ 25, 6, 40,Graphics. TOP|Graphics. LEFT);. setColor (10, 10, 210);tm = (int) ( (System. currentTimeMillis () - startTime) /1000);min = (tm/60) %60;sec = tm%60;. drawString ("Time: "+ min+": "+sec, 5, 55,Graphics. TOP|Graphics. LEFT);. setColor (c);();
}void continueGame () {= false;
}boolean pause = false;void run () {k = 0;g = getGraphics ();addgoldtime = 0;(true) {(! pause) {add = (int) (System. currentTimeMillis () /1000);(add%2 == 0 && add! = addgoldtime) {= add;++;[] mar = ms. getArray ();. act (mar);. move ();++;r = new Random ();(k % (r. nextInt (5) +5) == 0) {= 0;. add (mf. genNext ());
}(g);
}
}{. sleep (80);
} catch (InterruptedException ex) {. printStackTrace ();
}(hp <= 0) break;
}. setColor (255, 0, 120);. drawString ("G A M E O V E R", 30, 30,Graphics. TOP|Graphics. LEFT);();{. sleep (200);
} catch (InterruptedException ex) {. printStackTrace ();
}. destroyApp (false);. notifyDestroyed ();
}
}
// Tower. java
import javax. microedition. lcdui. Graphics;javax. microedition. lcdui. Image;javax. microedition. lcdui. game. Sprite;abstract class Tower {public boolean attack (Monster [] m);protected void upgrade ();sp;xp;dmg;range;gold;cooldown;lastactiontime = 0;info;img;Tower (int xp, int dmg, int range, int gold, int cooldown, Image img,String info) {. xp = xp;. dmg = dmg;. range = range;. gold = gold;. cooldown = cooldown;. img = Image. createImage (img, 0, 0, 36, 36, Sprite. TRANS_NONE);= new Sprite (img, 36, 36);. info = info;
}void setXY (int x, int y) {. setPosition (x, y);
}void paint (Graphics g) {. paint (g);
}void paintInfo (Graphics g, int x, int y) {. drawImage (img, x, y, Graphics. TOP|Graphics. LEFT);(g);tp = getCentPosition ();color = g. getColor ();. setColor (255, 0, 0);. drawRect (tp. x - range, tp. y - range, range*2, range*2);. drawString ("Dmg: \n"+ dmg, x+37, y, Graphics. TOP|Graphics. LEFT);. setColor (0, 30, 200);. drawString ("Range: "+ range, x, y+36, Graphics. TOP|Graphics. LEFT);. setColor (255, 245, 48);. drawString ("Gold: "+ gold, x, y+51, Graphics. TOP|Graphics. LEFT);. setColor (200, 200, 200);. drawString ("Cldn: "+ cooldown, x, y+66, Graphics. TOP|Graphics. LEFT);. setColor (0, 0, 0);. drawString (info, x-1, y-28, Graphics. TOP|Graphics. LEFT);. setColor (color);
}boolean isCooledDown () {time = System. currentTimeMillis ();(time - lastactiontime > cooldown*100);
}boolean isInRange (Monster m) {p = m. getCentPosition ();tp = getCentPosition ();( (p. x <= tp. x + range && p. x >= tp. x - range) &&
(p. y <= tp. y + range) && (p. y >= tp. y - range));
}void act (Monster [] m) {(isCooledDown ()) {(attack (m)) {++;();= (xp > 1000)? xp%1000: xp;
}= System. currentTimeMillis ();
}
}point getCentPosition () {new point (sp. getX () +sp. getWidth () /2,sp. getY () +sp. getHeight () /2);
}point getPosition () {new point (sp. getX (),sp. getY ());
}int getPrice () {gold;
}
// Towers. java
import javax. microedition. lcdui. Graphics;class Towers {[] arr;ntw = null;sell;Towers () {= new Tower [148];
}void act (Monster [] mar) {(int i=0; i<arr. length; i++) {(arr [i]! = null) {[i]. act (mar);
}
}
}void tryNew (Tower ntw) {. ntw = ntw;. setXY (-100, - 100);
}void cancelNew () {= null;
}void moveNew (int x, int y) {(ntw! = null) ntw. setXY (x, y);
}void paint (Graphics g) {(int i=0; i<arr. length; i++) {(arr [i]! = null) arr [i]. paint (g);
}(! sell && ntw! = null) ntw. paintInfo (g, 7,101);
}void paintInfo (Graphics g, int x, int y, int ind) {(arr [ind]! = null) arr [ind]. paintInfo (g,x,y);
}int put (GameField gf, int gold) {(ntw! = null) {(gold < ntw. getPrice ()) return - 1;p = ntw. getCentPosition ();= gf. getCellij (p. x, p. y);(! gf. isCellAvailable (p. x, p. y)) return - 1;= gf. getCellXY (p);(! isFree (p)) return - 1;. setXY (p. x, p. y);(int i=0; i<arr. length; i++) {(arr [i] == null) {[i] = ntw;();gold - arr [i]. gold;
}
}
}- 1;
}boolean isFree (point p) {(int i=0; i<arr. length; i++) {(arr [i]! = null) {(p. equals (arr [i]. getPosition ())) return false;
}
}true;
}int check (point p) {(int i=0; i<arr. length; i++) {(arr [i]! = null) {cur = arr [i]. getPosition ();(p. x > cur. x && p. x < cur. x + arr [i]. sp. getWidth () &&. y > cur. y && p. y < cur. y + arr [i]. sp. getHeight ()) return i;
}
}- 1;
}int remove (int i) {gold = arr [i]. gold;[i] =null;= false;gold;
}void changeState (boolean sell) {. sell = sell;
}
}
// Trek. java
import java. util. Stack;class Trek {st;Trek (Stack st) {. st = st;
}point getNext () {(point) st. peek ();
}boolean isEmty () {st. isEmpty ();
}void delNext () {. pop ();
}Trek copy () {st1 = new Stack ();. setSize (st. size ());(int i=0; i<st. size (); i++). setElementAt (st. elementAt (i), i);new Trek (st1);
}
}
// TrekBuilder. java
import java. util. Random;java. util. Stack;java. util. Vector;class TrekBuilder {[] treks = null;[] [] map = null;h,w,x0,y0;TrekBuilder (int h, int w, int x0, int y0) {. h = h;. w = w;. x0 = x0;. y0 = y0;
}Stack convertToXY (Stack st) {(int i=0; i<st. size (); i++) {x = ( (point) st. elementAt (i)). x;y = ( (point) st. elementAt (i)). y;
( (point) st. elementAt (i)). x = y*w + x0;
( (point) st. elementAt (i)). y = x*h + y0;
}. out. println ();st;
}boolean buildTreks (int [] [] map, int si, int sj) {. map = map;from = new Stack ();. push (new point (si, sj));v = findTrek (from);(v. isEmpty ()) return false;= new Trek [v. size ()];(int i =0; i<v. size (); i++) {s = (Stack) v. elementAt (i);. addElement (new point (si,sj));[i] = new Trek (convertToXY (s));
}true;
}boolean ok (int i, int j) {(map [i] [j] == GameField. ROAD);
}boolean finish (int i, int j) {(map [i] [j] == GameField. HOME);
}
// повертає вектор стеків шляху (шлях в клітинках, а не координатах)Vector findTrek (Stack from) {ret = new Vector ();[] neib = new point [4];f = (point) from. peek ();[0] = new point (f. x,f. y-1);[1] = new point (f. x,f. y+1);[2] = new point (f. x-1,f. y);[3] = new point (f. x+1,f. y);(int i=0; i<4; i++) {(neib [i]. x < map. length && neib [i]. y < map [0]. length && neib [i]. x>=0 && neib [i]. y>=0) {(finish (neib [i]. x,neib [i]. y)) {st = new Stack ();. push (neib [i]. copy ());. addElement (st);
}(ok (neib [i]. x,neib [i]. y) &&! from. contains (neib [i])) {. push (neib [i]);v = findTrek (from);. pop ();(int j = 0; j<v. size (); j++) {cur = (Stack) v. elementAt (j);. push (neib [i]. copy ());. addElement (cur);
}
}
}
}ret;
}Trek randTrek () {(treks == null) return null;{r = new Random ();i = r. nextInt (treks. length);treks [i]. copy ();
}
}Trek [] getTreks () {treks;
}
}
// point. java
public class point {int x;int y;point (int x, int y) {. x = x;. y = y;
}boolean equals (Object obj) {(obj instanceof point) {p = (point) obj;(x == p. x && y == p. y);
}super. equals (obj);point copy () {new point (x,y);
}
}