| C:\eclipse\workspace\Epsilon_03\src\GameCanvas.java |
1 /**
2 * GameCanvas contains the Canvas for the Epsilon game.
3 * Epsilon is a simple space-shooter-vertical-scroller game.
4 *
5 * Epsilon is available for non-commercial use only!
6 * You can learn from this sources and you can modify them
7 * for learning purposes.
8 *
9 * @author Ziga Hajdukovic
10 * @version 1.0
11 *
12 * CHANGES (01 --> 02)
13 * + new player sprite, p.png
14 * + added boss sprite boss1.png
15 * + added tileset t04.png
16 * + added enemy sprites e1.png, e2.png, e3.png
17 * + added fire.png, removed pf.png
18 * + added explosion sprite, exp.png
19 * + moved Image objects for sprites to spriteImage array
20 * + added tileImage
21 * + moved image loading to loadImages()
22 * + setClip in paintSprite
23 * + player sprite frames for left and right movement
24 * + multiple fire sprites + fire_cooldown_wait_frames
25 * + 20 FPS fixed frame rate
26 * + Vector explosionSprites
27 * + updateExplosionSprites()
28 * + doCollision() -> add explosion on collision
29 * + paint explosionSprites
30 * + Level class
31 * + loadLevel(), tile map, and sprite positions
32 * + space and tiles y and vy
33 * + load sprites positions
34 * + level paint
35 * + (scrolling background space layer)
36 * + (scrolling tile layer)
37 * + new (but still random) enemies
38 * + player game over explosion
39 *
40 *
41 * CHANGES (02 -> 03)
42 * + run() if(gameRunning)
43 * + run(), + midlet.display.callSerially(this);
44 * + midlet.startApp(), gameCanvas.run()
45 *
46 * + pause game screen
47 * + new menu scr paused game, with 2 softkey labels
48 * + resume on softkey1
49 * + main menu on softkey2
50 * + hidenotify()
51 * + player.vy += Level.tiles_scroll_vy
52 * + removed automatic enemy sprite generation
53 * + updated player screen edge collision detection
54 * + spriteData array
55 * + damage and score added to GameSprite, see doCollision
56 * + EXPLOSION_FRAME_SMOKE_START
57 * + move player with level scroll
58 * + enemy activation zone
59 * + remove out-of-screen enemies, updated!
60 * + doEnemyAI()
61 * + fire_cooldown_frames_counter moved to GameSprite
62 * + collision updated, vector loop inverted...
63 * + enemyFireSprites Vector, updateEnemyFireSprites() paint ...
64 * + doEnemyAI(), shooting enemies!
65 * + enemy fire sprites <-> player collision
66 * + boss big bada boom explosion, like players...
67 * + god_mode
68 * + boss, moving, shooting
69 * + view and enter hiscore screens
70 * + flow for score view and edit
71 * + import javax.microedition.rms.*;
72 * + import java.io.*;
73 * + loadHighscoreFromRMS(), add call to intro mode, when loading
74 * + isQualified(int score)
75 * + addHighscore(int score, String name)
76 * + getHighscoreData(), highscore helper function
77 * + saveHighscores(), highscore helper function
78 *
79 *
80 */
81
82 import java.util.*;
83
84 import javax.microedition.lcdui.*;
85
86 import javax.microedition.rms.*;
87 import java.io.*;
88
89 import com.nokia.mid.ui.FullCanvas;
90
91 class GameCanvas extends FullCanvas implements Runnable
92 //class GameCanvas extends Canvas implements Runnable
93 {
94 private final Game midlet;
95
96 public static int canvasWidth;
97 public static int canvasHeight;
98
99 public static boolean gameRunning;
100
101 public static final int SOFTKEY_1 = -6;
102 public static final int SOFTKEY_2 = -7;
103
104 public static boolean menu_command_1;
105 public static boolean menu_command_2;
106
107 public static boolean command_up;
108 public static boolean command_down;
109 public static boolean command_left;
110 public static boolean command_right;
111 public static boolean command_fire;
112
113 public static byte mode;
114 public static final byte MODE_INTRO = 0;
115 public static final byte MODE_GAME = 1;
116 public static final byte MODE_MENU = 2;
117
118 public static int loadingCounter;
119
120 public static Image imgIntro;
121 public static Image imgBackground;
122
123 public static Image[] spriteImages;
124 public static final byte SPRITE_IMAGE_PLAYER = 0;
125 public static final byte SPRITE_IMAGE_FIRE = 1;
126 public static final byte SPRITE_IMAGE_BOSS1 = 2;
127 public static final byte SPRITE_IMAGE_ENEMY1 = 3;
128 public static final byte SPRITE_IMAGE_ENEMY2 = 4;
129 public static final byte SPRITE_IMAGE_ENEMY3 = 5;
130 public static final byte SPRITE_IMAGE_EXPLOSION = 6;
131 public static final byte SPRITE_IMAGE_MAX = 7;
132
133 public static final byte SPRITE_FRAME_PLAYER_LEFT = 0;
134 public static final byte SPRITE_FRAME_PLAYER_CENTER = 1;
135 public static final byte SPRITE_FRAME_PLAYER_RIGHT = 2;
136
137 public static final byte PLAYER_FIRE_COOLDOWN_WAIT_FRAMES = 5;
138 public static final byte ENEMY_FIRE_COOLDOWN_WAIT_FRAMES = 40;
139
140 public static Image tileImage;
141
142 public static int menuScreen;
143 public static final byte MENU_SCR_MAIN = 0;
144 public static final byte MENU_SCR_TEXT_ABOUT = 1;
145 public static final byte MENU_SCR_TEXT_HELP = 2;
146 public static final byte MENU_SCR_TEXT_GAME_OVER = 3;
147 public static final byte MENU_SCR_PAUSE_GAME = 4;
148 public static final byte MENU_SCR_HISCORE_ENTER = 5;
149 public static final byte MENU_SCR_HISCORE_VIEW = 6;
150
151 public static int menuSelectedOption;
152 public static final byte MENU_OPTION_MAIN_NEW_GAME = 0;
153 public static final byte MENU_OPTION_MAIN_HISCORES = 1;
154 public static final byte MENU_OPTION_MAIN_ABOUT = 2;
155 public static final byte MENU_OPTION_MAIN_HELP = 3;
156 public static final byte MENU_OPTION_MAIN_MAX = 3;
157
158 public static final String[] menuScreenMainOptions = { "New game", "Highscore", "About", "Help"};
159
160 public static final String[][] menuScreensTextLines = {
161 { "" },
162 { "About", "", "Epsilon v0.1", "(c) 2005 zigah." },
163 { "Help", "", "Press LEFT, RIGHT, ", "UP or DOWN", "to move and", "FIRE to shoot." },
164 { "Game Over", "", "Well done!"},
165 { "","Paused!"},
166 };
167
168
169 // Highscore
170 public static final byte MAX_SCORES = 5;
171 public static final String SCORE_DB_NAME = "EPS";
172 public static String hiNames[] = { "JOE", "DOE", "LAA", "BIG", "MAC" };
173 public static int hiScores[] = { 300, 200, 150, 100, 50 };
174 private static RecordStore db;
175
176 public static final char[] HI_NAME_ENTER_DEFAULT = {"A","A","A"};
177 public static final byte MAX_HI_NAME_LENGTH = 3;
178 public static char[] hi_name_enter = new char[MAX_HI_NAME_LENGTH];
179 public static byte hi_name_enter_char_pos;
180
181
182 // for debug purposes
183 public boolean god_mode;
184
185 public static GameSprite player;
186 public static Vector playerFireSprites;
187 public static int player_score;
188 public static int player_gameover_explosion_counter;
189 public static final byte PLAYER_GAMEOVER_EXPLOSION_COUNTER_MAX = 40;
190
191 public static boolean activate_boss_energy_bar;
192
193 public static int boss_dead_explosion_counter;
194 public static final byte BOSS_DEAD_EXPLOSION_COUNTER_MAX = 80;
195
196 public static final byte FIRE_VELOCITY = 8;
197 public static final byte MAX_ENEMY_FIRE = 6;
198
199 public static Vector enemyFireSprites;
200
201 public static final byte MAX_ENERGY_PLAYER = 100;
202 public static final byte MAX_ENERGY_PLAYER_FIRE = 10;
203 public static final byte MAX_ENERGY_ENEMY = 10;
204
205 public static Vector explosionSprites;
206
207 public static byte max_enemy_sprites = 3;
208
209 public static final short FIXED_FRAME_TIME = 40;
210
211 public static int global_frame_counter;
212
213 public static Random random = new Random();
214
215 GameCanvas(Game midlet)
216 {
217 this.midlet = midlet;
218
219 canvasWidth = this.getWidth();
220 canvasHeight = this.getHeight();
221
222 loadingCounter = 0;
223 menuScreen = MENU_SCR_MAIN;
224 menuSelectedOption = MENU_OPTION_MAIN_NEW_GAME;
225 mode = MODE_INTRO;
226 gameRunning = true;
227 }
228
229 public synchronized void run()
230 {
231 if(gameRunning)
232 {
233 long frameStartTime = System.currentTimeMillis();
234 global_frame_counter++;
235
236 repaint();
237 //serviceRepaints();
238 midlet.display.callSerially(this);
239
240 switch (mode) {
241 case MODE_INTRO:
242
243 try
244 {
245 if (loadingCounter == 0)
246 {
247 imgIntro = Image.createImage("/intro.png");
248 }
249 else if (loadingCounter == 1)
250 {
251 loadImages();
252 loadHighscoreFromRMS();
253 }
254 else
255 {
256 // view the intro splash image for a while
257 // it would be better to let user skip intro by pressing a key
258 // but, in a full game project, loading would take forever, anyway, so...
259 try {
260 Thread.sleep(1000);
261 } catch (Exception e) {};
262 // switch mode
263 mode = MODE_MENU;
264 }
265 loadingCounter++;
266 }
267 catch (Exception e) {
268 // ignore loading exception
269 };
270 break;
271
272 case MODE_MENU:
273
274 if (command_up) {
275 command_up = false;
276 menuSelectedOption--;
277 if (menuSelectedOption < 0)
278 menuSelectedOption = 0;
279
280 if (menuScreen == MENU_SCR_HISCORE_ENTER)
281 {
282 if ( hi_name_enter[hi_name_enter_char_pos] > "A")
283 hi_name_enter[hi_name_enter_char_pos]--;
284 }
285 }
286 if (command_down) {
287 command_down = false;
288 menuSelectedOption++;
289 if ((menuScreen == MENU_SCR_MAIN) &&
290 (menuSelectedOption > MENU_OPTION_MAIN_MAX))
291 menuSelectedOption = MENU_OPTION_MAIN_MAX;
292
293 if (menuScreen == MENU_SCR_HISCORE_ENTER)
294 {
295 if ( hi_name_enter[hi_name_enter_char_pos] < "Z")
296 hi_name_enter[hi_name_enter_char_pos]++;
297 }
298 }
299 if (command_left) {
300 command_left = false;
301
302 if (menuScreen == MENU_SCR_HISCORE_ENTER)
303 {
304 if (hi_name_enter_char_pos > 0)
305 hi_name_enter_char_pos--;
306 }
307 }
308 if (command_right) {
309 command_left = false;
310
311 if (menuScreen == MENU_SCR_HISCORE_ENTER)
312 {
313 if (hi_name_enter_char_pos < MAX_HI_NAME_LENGTH-1)
314 hi_name_enter_char_pos++;
315 }
316 }
317
318 if (menu_command_1)
319 {
320 menu_command_1 = false;
321 if (menuScreen == MENU_SCR_MAIN)
322 {
323 switch(menuSelectedOption)
324 {
325 case MENU_OPTION_MAIN_NEW_GAME:
326 startGame();
327 break;
328
329 case MENU_OPTION_MAIN_HISCORES:
330 menuScreen = MENU_SCR_HISCORE_VIEW;
331 break;
332
333 case MENU_OPTION_MAIN_ABOUT:
334 menuScreen = MENU_SCR_TEXT_ABOUT;
335 break;
336
337 case MENU_OPTION_MAIN_HELP:
338 menuScreen = MENU_SCR_TEXT_HELP;
339 break;
340 }
341 } else if ( menuScreen == MENU_SCR_PAUSE_GAME) {
342 // resume pressed
343 mode = MODE_GAME;
344 } else if ( menuScreen == MENU_SCR_TEXT_GAME_OVER) {
345 // ok pressed
346 if ( isQualified( player_score ) != -1 )
347 {
348 menuScreen = MENU_SCR_HISCORE_ENTER;
349 // init enter name character position
350 hi_name_enter_char_pos = 0;
351 hi_name_enter = HI_NAME_ENTER_DEFAULT;
352 } else {
353 menuScreen = MENU_SCR_HISCORE_VIEW;
354 }
355 } else if ( menuScreen == MENU_SCR_HISCORE_ENTER) {
356 // save highscore
357
358 String name = new String(hi_name_enter);
359 addHighscore(player_score, name);
360
361 menuScreen = MENU_SCR_HISCORE_VIEW;
362 } else {
363 // all other menu screens go to main menu when OK is selected
364 menuScreen = MENU_SCR_MAIN;
365 }
366 }
367 if (menu_command_2)
368 {
369 menu_command_2 = false;
370
371 // back to main menu, when end game selected
372 if ( menuScreen == MENU_SCR_PAUSE_GAME) {
373 menuScreen = MENU_SCR_MAIN;
374 } else if (menuScreen == MENU_SCR_MAIN)
375 {
376 midlet.exitRequested();
377 }
378 }
379
380 // delay, so the keypress events get their turn...
381 try {
382 Thread.sleep(40);
383 } catch (Exception e) {};
384 break;
385
386 case MODE_GAME:
387
388 // scroll space and tile background
389 Level.space_scroll_y += Level.space_scroll_vy;
390 Level.tiles_scroll_y += Level.tiles_scroll_vy;
391
392 // top of the level (boss) reached, stop scrolling
393 if (Level.tiles_scroll_y < 0) {
394 Level.tiles_scroll_y = 0;
395 Level.tiles_scroll_vy = 0;
396 }
397
398 // int tiles_scroll_x_range = (Level.LEVEL_WIDTH * Level.TILE_SIZE - canvasWidth);
399 // int player_x_range = canvasWidth - player.w;
400 // Level.tiles_scroll_x = player.x * tiles_scroll_x_range / player_x_range;
401
402
403 // update scroll_x
404 Level.tiles_scroll_x = player.x + player.w/2 - canvasWidth/2;
405 if (Level.tiles_scroll_x > (Level.LEVEL_WIDTH * Level.TILE_SIZE - canvasWidth)) {
406 Level.tiles_scroll_x = (Level.LEVEL_WIDTH * Level.TILE_SIZE - canvasWidth);
407 }
408
409 if (Level.tiles_scroll_x < 0) {
410 Level.tiles_scroll_x = 0;
411 }
412
413 updatePlayerSprite();
414 updatePlayerFireSprites();
415
416 updateEnemySprites();
417 updateEnemyFireSprites();
418
419 updateExplosionSprites();
420
421 // check player <-> enemy <-> player fire collision
422 doCollision();
423
424 // check for game end
425 if ( (player.energy <= 0) && (player_gameover_explosion_counter == 0) )
426 {
427 // TODO: lose screen
428 endGame();
429 }
430 // check for game end
431 if ( (Level.boss.energy <= 0) && (boss_dead_explosion_counter == 0) )
432 {
433 // TODO: win screen
434 endGame();
435 }
436 if (menu_command_2)
437 {
438 menu_command_2 = false;
439 // on softkey_2 activate pause game menu
440 mode = MODE_MENU;
441 menuScreen = MENU_SCR_PAUSE_GAME;
442 }
443
444 // calc. delay
445 long frameTime = System.currentTimeMillis() - frameStartTime;
446 if (frameTime < FIXED_FRAME_TIME)
447 {
448 try {
449 Thread.sleep( FIXED_FRAME_TIME - frameTime );
450 } catch (Exception e) {};
451 }
452 break;
453 }
454 }
455 }
456
457 public synchronized void paint(Graphics g)
458 {
459 switch(mode)
460 {
461 case MODE_INTRO:
462
463 g.setColor(0x000000);
464 g.fillRect(0, 0, canvasWidth, canvasHeight);
465 if (imgIntro != null) {
466 g.drawImage(imgIntro, canvasWidth/2, canvasHeight/2, Graphics.VCENTER | Graphics.HCENTER);
467 }
468
469 // g.setColor(0xFFFFFF);
470 // g.drawString(""+loadingCounter,0,0,Graphics.LEFT | Graphics.TOP);
471
472 break;
473
474 case MODE_MENU:
475
476 Level.paintSpaceBackground(g);
477
478 switch(menuScreen)
479 {
480 case MENU_SCR_MAIN:
481 paintMainMenu(g);
482 break;
483
484 case MENU_SCR_TEXT_ABOUT:
485 case MENU_SCR_TEXT_HELP:
486 case MENU_SCR_TEXT_GAME_OVER:
487 case MENU_SCR_PAUSE_GAME:
488 paintTextMenu(g, menuScreen);
489 break;
490 case MENU_SCR_HISCORE_VIEW:
491 paintHiscoreView(g);
492 break;
493 case MENU_SCR_HISCORE_ENTER:
494 paintHiscoreEnter(g);
495 break;
496 }
497 break;
498
499 case MODE_GAME:
500
501 Level.paintSpaceBackground(g);
502 Level.paintTiles(g);
503
504 paintSprite( g, player );
505
506 for (int i=0; i < Level.enemySprites.size(); i++)
507 paintSprite( g, (GameSprite) Level.enemySprites.elementAt(i) );
508
509 for (int i=0; i < explosionSprites.size(); i++)
510 paintSprite( g, (GameSprite) explosionSprites.elementAt(i) );
511
512 for (int i=0; i < playerFireSprites.size(); i++)
513 paintSprite( g, (GameSprite) playerFireSprites.elementAt(i));
514
515 for (int i=0; i < enemyFireSprites.size(); i++)
516 paintSprite( g, (GameSprite) enemyFireSprites.elementAt(i));
517
518 paintStatusBars(g);
519
520 break;
521 }
522 }
523
524 public void keyPressed(int key)
525 {
526 switch(key) {
527 case SOFTKEY_1:
528 menu_command_1 = true;
529 break;
530
531 case SOFTKEY_2:
532 menu_command_2 = true;
533 break;
534
535 // TODO: debug, temp, remove
536 case KEY_POUND:
537 god_mode = !god_mode;
538 break;
539 // TODO: debug, temp, remove
540 case KEY_STAR:
541 Level.tiles_scroll_vy *= 2;
542 break;
543 }
544
545 int game_action = getGameAction(key);
546
547 switch(game_action)
548 {
549 case UP:
550 command_up = true;
551 break;
552
553 case DOWN:
554 command_down = true;
555 break;
556
557 case LEFT:
558 command_left = true;
559 break;
560
561 case RIGHT:
562 command_right = true;
563 break;
564
565 case FIRE:
566 command_fire = true;
567 menu_command_1 = true;
568 break;
569 }
570 }
571
572 public void keyReleased(int key)
573 {
574 int game_action = getGameAction(key);
575
576 switch(game_action)
577 {
578 case UP:
579 command_up = false;
580 break;
581
582 case DOWN:
583 command_down = false;
584 break;
585
586 case LEFT:
587 command_left = false;
588 break;
589
590 case RIGHT:
591 command_right = false;
592 break;
593
594 case FIRE:
595 command_fire = false;
596 menu_command_1 = false;
597 break;
598 }
599 }
600
601 public void paintMainMenu(Graphics g)
602 {
603 // init font
604 g.setFont(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_LARGE));
605 int menu_y = canvasHeight/2 - 45;
606 for(int i=0; i <= MENU_OPTION_MAIN_MAX; i++)
607 {
608 // paint the main menu options
609 if (i == menuSelectedOption) {
610 g.setColor(0xFFCC00);
611 } else {
612 g.setColor(0xFFFFFF);
613 }
614
615 g.drawString(menuScreenMainOptions[i], canvasWidth/2, menu_y, Graphics.HCENTER | Graphics.TOP);
616
617 menu_y += 20;
618 }
619 g.setFont(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_MEDIUM));
620 g.setColor(0xFFFFFF);
621 // paint softkey_1 label
622 g.drawString("Select", 2, canvasHeight-2, Graphics.LEFT | Graphics.BOTTOM);
623 // paint softkey_2 label
624 g.drawString("Exit", canvasWidth-2, canvasHeight-2, Graphics.RIGHT | Graphics.BOTTOM);
625 }
626
627 public void paintTextMenu(Graphics g, int screen)
628 {
629 g.setFont(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_MEDIUM));
630 g.setColor(0xFFFFFF);
631 int text_line_y = 10;
632 for(int line=0; line < menuScreensTextLines[screen].length; line++)
633 {
634 // paint all the lines of text in this menu screen
635 g.drawString(menuScreensTextLines[screen][line], canvasWidth/2, text_line_y, Graphics.HCENTER | Graphics.TOP);
636
637 text_line_y += 15;
638 }
639 if (screen == MENU_SCR_TEXT_GAME_OVER)
640 {
641 // print score in the game over screen
642 text_line_y += 15;
643 g.drawString("Score: "+ player_score, canvasWidth/2, text_line_y, Graphics.HCENTER | Graphics.TOP);
644 }
645
646 g.setFont(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_MEDIUM));
647
648 if (menuScreen == MENU_SCR_PAUSE_GAME )
649 {
650 // paint softkey_1 label
651 g.drawString("Resume", 1, canvasHeight-1, Graphics.LEFT | Graphics.BOTTOM);
652 // paint softkey_2 label
653 g.drawString("End game", canvasWidth-1, canvasHeight-1, Graphics.RIGHT | Graphics.BOTTOM);
654 }
655 else
656 {
657 // paint softkey_1 label
658 g.drawString("Ok", 1, canvasHeight-1, Graphics.LEFT | Graphics.BOTTOM);
659 }
660 }
661
662 public static void paintHiscoreView(Graphics g)
663 {
664 g.setColor(0xFFFFFF);
665
666 g.drawString("Highscore!", canvasWidth/2, 2, Graphics.HCENTER | Graphics.TOP);
667
668 for (int i=0; i < MAX_SCORES; i++)
669 {
670 g.drawString(hiNames[i], canvasWidth*1/4, 20 + 20*i, Graphics.LEFT | Graphics.TOP);
671 g.drawString(""+hiScores[i], canvasWidth*3/4, 20 + 20*i, Graphics.RIGHT | Graphics.TOP);
672 }
673
674 // paint softkey_1 label
675 g.drawString("Ok", 1, canvasHeight-1, Graphics.LEFT | Graphics.BOTTOM);
676 }
677 public static void paintHiscoreEnter(Graphics g)
678 {
679 g.setColor(0xFFFFFF);
680
681 g.drawString("Enter name:", canvasWidth/2, canvasHeight*1/4, Graphics.HCENTER | Graphics.BOTTOM);
682
683 for (int i=0; i < MAX_HI_NAME_LENGTH; i++)
684 {
685 if (i == hi_name_enter_char_pos)
686 g.setColor(0xFFFF00);
687 else
688 g.setColor(0xFFFFFF);
689 g.drawChar(hi_name_enter[i], canvasWidth/2 - 12 + 12*i, canvasHeight*2/4, Graphics.HCENTER | Graphics.BOTTOM);
690 }
691
692 g.setColor(0xFFFFFF);
693 g.drawString("Score: "+ player_score, canvasWidth/2, canvasHeight*3/4, Graphics.HCENTER | Graphics.BOTTOM);
694
695 // paint softkey_1 label
696 g.drawString("Save", 1, canvasHeight-1, Graphics.LEFT | Graphics.BOTTOM);
697 }
698
699 public static void paintSprite(Graphics g, GameSprite spr)
700 {
701 // set the clip window
702 int dx = spr.x - Level.tiles_scroll_x;
703 int dy = spr.y - Level.tiles_scroll_y;
704 int cx = dx;
705 int cy = dy;
706 int cw = GameSprite.spriteImageFrameWidths[spr.imageId];
707 int ch = spr.h;
708 g.setClip(cx, cy, cw, ch);
709
710 // check clip window coordinates, if out of screen
711 if (cx < 0)
712 {
713 cw += cx;
714 cx = 0;
715 }
716 if (cy < 0)
717 {
718 ch += cy;
719 cy = 0;
720 }
721
722 // calculate the x coordinate of where to start the image paint, so our frame will be seen through the clip window
723 dx = dx - spr.frameId * GameSprite.spriteImageFrameWidths[spr.imageId];
724
725 // paint the sprites image at the calculated coordinates
726 g.drawImage(spriteImages[spr.imageId], dx, dy, Graphics.TOP | Graphics.LEFT);
727
728 // restore the clip window
729 g.setClip(0, 0, canvasWidth, canvasHeight);
730 }
731
732 public static void paintStatusBars(Graphics g)
733 {
734 int max_energy_bar_width = canvasWidth/2;
735
736 // paint the energy bar
737 g.setColor(0xFFFFFF);
738 g.drawRect(5, 5, max_energy_bar_width + 1, 4);
739 g.setColor(0xFFCC00);
740 g.fillRect(6, 6, player.energy * max_energy_bar_width / player.energyMax, 3);
741
742 if ( activate_boss_energy_bar)
743 {
744 // paint the energy bar
745 g.setColor(0xFFFFFF);
746 g.drawRect(5, 12, max_energy_bar_width + 1, 4);
747 g.setColor(0xFF9900);
748 g.fillRect(6, 13, Level.boss.energy * max_energy_bar_width / Level.boss.energyMax, 3);
749 }
750
751
752 // paint the score count
753 g.setColor(0xFFFFFF);
754 g.setFont(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_PLAIN, Font.SIZE_SMALL));
755 g.drawString(""+player_score, canvasWidth-4, 8, Graphics.TOP | Graphics.RIGHT);
756 }
757
758 public void startGame()
759 {
760 // switch to game mode
761 mode = MODE_GAME;
762
763 player_score = 0;
764 player_gameover_explosion_counter = -1;
765
766 activate_boss_energy_bar = false;
767 boss_dead_explosion_counter = -1;
768
769 // initialize the explosion sprite list
770 explosionSprites = new Vector();
771
772 // initialize the player fire list
773 playerFireSprites = new Vector();
774
775 // initialize the enemy fire list
776 enemyFireSprites = new Vector();
777
778 // init level tiles position to horizontal center and vertical bottom
779 Level.tiles_scroll_x = Level.LEVEL_WIDTH * Level.TILE_SIZE / 2 - canvasWidth /2;
780 Level.tiles_scroll_y = Level.LEVEL_HEIGHT * Level.TILE_SIZE - canvasHeight - 1;
781
782 Level.loadLevel((byte) 1);
783 }
784
785 public void endGame()
786 {
787 // back to menu mode, set the game over text screen
788 mode = MODE_MENU;
789 menuScreen = MENU_SCR_TEXT_GAME_OVER;
790
791 // clean up
792 player = null;
793 Level.enemySprites = null;
794 }
795
796 public void updateEnemySprites()
797 {
798 // move enemy sprites
799 int i = Level.enemySprites.size()-1;
800 while (i >= 0)
801 {
802 GameSprite enemy = (GameSprite) Level.enemySprites.elementAt(i);
803
804 if ( (enemy.y - Level.tiles_scroll_y > canvasHeight) )
805 {
806 // remove out-of-screen enemies
807 Level.enemySprites.removeElementAt(i);
808 i--;
809 }
810 else
811 {
812 //System.out.println(enemy.y +"-"+ Level.tiles_scroll_y +">"+ Level.ENEMY_AI_ACTIVATION_ZONE_Y);
813 // do the AI move, shoot and other tricks
814 if ( enemy.y - Level.tiles_scroll_y > Level.ENEMY_AI_ACTIVATION_ZONE_Y )
815 {
816 doEnemyAI( enemy );
817 i--;
818 } else {
819 // optimization
820 // but enemySprites must be ordered by the sprites" y coordinate !
821 // they are in this case, because of loadLevel
822 break;
823 }
824 }
825 }
826 if (boss_dead_explosion_counter > 0)
827 {
828 boss_dead_explosion_counter--;
829 }
830 }
831
832 public void doEnemyAI( GameSprite sprite )
833 {
834 //System.out.println("doing enemy ai for "+sprite.spriteId);
835
836 switch (sprite.spriteId)
837 {
838 case Level.SPRITE_ID_ENEMY1:
839 break;
840 case Level.SPRITE_ID_ENEMY1_SHOOTING:
841
842 shootEnemyFire(sprite, Level.SPRITE_ID_FIRE2, (byte) 1);
843
844 break;
845 case Level.SPRITE_ID_ENEMY2:
846 break;
847 case Level.SPRITE_ID_ENEMY2_SHOOTING:
848
849 shootEnemyFire(sprite, Level.SPRITE_ID_FIRE2, (byte) 1);
850
851 break;
852 case Level.SPRITE_ID_ENEMY3:
853
854 // animate sprite to next sprite frame every 3 frames
855 if (global_frame_counter % 3 == 0)
856 sprite.frameId = (byte) ( (sprite.frameId + 1) % (sprite.frameIdMax+1));
857
858 break;
859 case Level.SPRITE_ID_BOSS1:
860
861 activate_boss_energy_bar = true;
862
863 // boss fire
864 if (sprite.fire_cooldown_frames_counter <= 0)
865 {
866 GameSprite new_fire;
867
868 new_fire = new GameSprite(Level.SPRITE_ID_FIRE2);
869 new_fire.frameId = 1;
870 new_fire.x = sprite.x + sprite.w - 5 - new_fire.w;
871 new_fire.y = sprite.y + sprite.h;
872 new_fire.vy = sprite.vy + FIRE_VELOCITY/2;
873 enemyFireSprites.addElement(new_fire);
874
875 new_fire = new GameSprite(Level.SPRITE_ID_FIRE2);
876 new_fire.frameId = 1;
877 new_fire.x = sprite.x + 5;
878 new_fire.y = sprite.y + sprite.h;
879 new_fire.vy = sprite.vy + FIRE_VELOCITY/2;
880 enemyFireSprites.addElement(new_fire);
881
882 new_fire = new GameSprite(Level.SPRITE_ID_FIRE3);
883 new_fire.frameId = 2;
884 new_fire.x = sprite.x + sprite.w / 2 - new_fire.w / 2;
885 new_fire.y = sprite.y + sprite.h;
886 new_fire.vy = sprite.vy + FIRE_VELOCITY;
887 enemyFireSprites.addElement(new_fire);
888
889 sprite.fire_cooldown_frames_counter = ENEMY_FIRE_COOLDOWN_WAIT_FRAMES;
890 } else {
891 // extra cooldown, boss only
892 sprite.fire_cooldown_frames_counter--;
893 }
894
895 // boss move
896 if (sprite.x + sprite.w > (Level.LEVEL_WIDTH * Level.TILE_SIZE) - (Level.TILE_SIZE/2) )
897 sprite.vx = -1;
898 if (sprite.x < Level.TILE_SIZE/2 )
899 sprite.vx = 1;
900
901 break;
902 }
903
904 // move the sprite
905 sprite.x += sprite.vx;
906 sprite.y += sprite.vy;
907
908 if (sprite.fire_cooldown_frames_counter > 0)
909 sprite.fire_cooldown_frames_counter--;
910 }
911
912 public GameSprite shootEnemyFire(GameSprite sprite, byte fire_sprite_id, byte fire_sprite_frame_id)
913 {
914 GameSprite new_fire = null;
915 if (enemyFireSprites.size() < MAX_ENEMY_FIRE && sprite.fire_cooldown_frames_counter <= 0)
916 {
917 // System.out.println("adding fire sprite ... ");
918 new_fire = new GameSprite(fire_sprite_id);
919 new_fire.frameId = fire_sprite_frame_id;
920 new_fire.x = sprite.x + sprite.w / 2;
921 new_fire.y = sprite.y + sprite.h;
922 new_fire.vy = sprite.vy + FIRE_VELOCITY/2;
923 enemyFireSprites.addElement(new_fire);
924
925 sprite.fire_cooldown_frames_counter = ENEMY_FIRE_COOLDOWN_WAIT_FRAMES;
926
927 }
928 return new_fire;
929 }
930
931 public void updatePlayerSprite()
932 {
933 // move player with level scroll
934 player.y += Level.tiles_scroll_vy;
935
936 if (player_gameover_explosion_counter > 0)
937 {
938 player_gameover_explosion_counter--;
939 }
940 else
941 {
942 // move player sprite on command, and check player <-> screen edge collision
943 if (command_up)
944 {
945 player.y -= player.vy;
946 if (player.y < Level.tiles_scroll_y)
947 player.y = Level.tiles_scroll_y;
948 }
949 if (command_down)
950 {
951 player.y += player.vy;
952 // level edge collision
953 if (player.y - Level.tiles_scroll_y > canvasHeight - player.h)
954 player.y = Level.tiles_scroll_y + canvasHeight - player.h;
955 }
956 player.frameId = SPRITE_FRAME_PLAYER_CENTER;
957 if (command_left)
958 {
959 player.x -= player.vx;
960 if (player.x < Level.tiles_scroll_x)
961 player.x = Level.tiles_scroll_x;
962 player.frameId = SPRITE_FRAME_PLAYER_LEFT;
963 }
964 if (command_right)
965 {
966 player.x += player.vx;
967 if (player.x - Level.tiles_scroll_x > canvasWidth - player.w)
968 player.x = Level.tiles_scroll_x + canvasWidth - player.w;
969 player.frameId = SPRITE_FRAME_PLAYER_RIGHT;
970 }
971 if (command_fire)
972 {
973 if (player.fire_cooldown_frames_counter <= 0)
974 {
975 GameSprite fire = new GameSprite(Level.SPRITE_ID_FIRE1);
976 fire.x = player.x + player.w/2 - fire.w/2;
977 fire.y = player.y;
978 fire.vy = -FIRE_VELOCITY + Level.tiles_scroll_vy;
979 playerFireSprites.addElement(fire);
980
981 player.fire_cooldown_frames_counter = PLAYER_FIRE_COOLDOWN_WAIT_FRAMES;
982 }
983 }
984 if (player.fire_cooldown_frames_counter > 0)
985 player.fire_cooldown_frames_counter--;
986 if ( god_mode )
987 player.fire_cooldown_frames_counter -= 2;
988 }
989 }
990
991 public void doCollision()
992 {
993
994 int i = Level.enemySprites.size() - 1;
995 while (i >= 0 )
996 {
997 GameSprite enemy = (GameSprite) Level.enemySprites.elementAt(i);
998
999 // check player <-> enemy collision
1000 if (player.collidesWith(enemy))
1001 {
1002 // add explosion
1003 addExplosion(enemy);
1004 addExplosion(player);
1005
1006 // player loses energy
1007 if ( !god_mode )
1008 player.energy -= enemy.damage;
1009
1010 if (player.energy <= 0 && player_gameover_explosion_counter < 0)
1011 {
1012 player_gameover_explosion_counter = PLAYER_GAMEOVER_EXPLOSION_COUNTER_MAX;
1013 }
1014
1015 // level boss does not take damage from colliding with player
1016 if (enemy != Level.boss)
1017 enemy.energy -= player.damage;
1018
1019 }
1020
1021 // check the player fire sprites <-> enemy collision
1022 int j = playerFireSprites.size() - 1;
1023 while (j >= 0)
1024 {
1025 GameSprite fire = (GameSprite) playerFireSprites.elementAt(j);
1026 if (fire.collidesWith(enemy))
1027 {
1028 enemy.energy -= fire.damage;
1029
1030 // add explosions
1031 addExplosion(enemy);
1032
1033 // destroy player fire
1034 playerFireSprites.removeElementAt(j);
1035 j--;
1036 }
1037 j--;
1038 }
1039 // remove dead enemies
1040 if (enemy.energy <= 0)
1041 {
1042 Level.enemySprites.removeElementAt(i);
1043 i--;
1044 // increase player score
1045 player_score += enemy.score;
1046
1047 // if boss sprite
1048 if (enemy.spriteId == Level.SPRITE_ID_BOSS1)
1049 {
1050 boss_dead_explosion_counter = BOSS_DEAD_EXPLOSION_COUNTER_MAX;
1051 }
1052 }
1053
1054 i--;
1055 }
1056 // check the enemy fire sprites <-> player collision
1057 int j = enemyFireSprites.size() - 1;
1058 while (j >= 0)
1059 {
1060 GameSprite fire = (GameSprite) enemyFireSprites.elementAt(j);
1061 if (fire.collidesWith(player))
1062 {
1063 if ( !god_mode )
1064 player.energy -= fire.damage;
1065
1066 // add explosions
1067 addExplosion(player);
1068
1069 // destroy enemy fire
1070 enemyFireSprites.removeElementAt(j);
1071 j--;
1072 }
1073 j--;
1074 }
1075 }
1076
1077 public void updatePlayerFireSprites()
1078 {
1079 for (int i=0; i < playerFireSprites.size(); i++)
1080 {
1081 GameSprite fire = (GameSprite) playerFireSprites.elementAt(i);
1082 fire.y += fire.vy;
1083
1084 // destroy player fire when out of screen
1085 if ( (fire.y + fire.h) < Level.tiles_scroll_y)
1086 {
1087 playerFireSprites.removeElementAt(i);
1088 // only increase counter, if not removed from list
1089 i--;
1090 }
1091 }
1092 }
1093
1094 public void updateEnemyFireSprites()
1095 {
1096 for (int i=0; i < enemyFireSprites.size(); i++)
1097 {
1098 // System.out.println("updating enemy fire sprite...");
1099
1100 GameSprite fire = (GameSprite) enemyFireSprites.elementAt(i);
1101 fire.y += fire.vy;
1102
1103 // destroy enemy fire when out of screen
1104 if ( fire.y > Level.tiles_scroll_y + canvasHeight)
1105 {
1106 enemyFireSprites.removeElementAt(i);
1107 // only increase counter, if not removed from list
1108 i--;
1109 }
1110 }
1111 }
1112
1113 public void updateExplosionSprites()
1114 {
1115 for (int i=0; i < explosionSprites.size(); i++)
1116 {
1117 GameSprite exp = (GameSprite) explosionSprites.elementAt(i);
1118
1119 // animate explosion!
1120 exp.frameId++;
1121
1122 if (exp.frameId > exp.frameIdMax)
1123 {
1124 explosionSprites.removeElementAt(i);
1125 // only increase counter, if not removed from list
1126 i--;
1127 }
1128 }
1129 // create a big bada boom player gameover explosion
1130 // 7 - max explosion frames
1131 // 2 - add an explosion every 2nd frame
1132 if (player_gameover_explosion_counter > 7 &&
1133 player_gameover_explosion_counter % 2 == 0
1134 )
1135 {
1136 GameSprite new_exp = addExplosion(player);
1137 new_exp.x += random.nextInt() % player.w;
1138 new_exp.y += random.nextInt() % player.h;
1139 }
1140 // create a big bada boom boss gameover explosion
1141 // 7 - max explosion frames
1142 // 2 - add an explosion every 2nd frame
1143 if (boss_dead_explosion_counter > 7 &&
1144 boss_dead_explosion_counter % 2 == 0
1145 )
1146 {
1147 GameSprite new_exp = addExplosion(Level.boss);
1148 new_exp.x += random.nextInt() % Level.boss.w;
1149 new_exp.y += random.nextInt() % Level.boss.h;
1150 }
1151 }
1152
1153 public static void loadImages()
1154 {
1155 try {
1156
1157 spriteImages = new Image[SPRITE_IMAGE_MAX];
1158 spriteImages[SPRITE_IMAGE_PLAYER] = Image.createImage("/p.png");
1159 spriteImages[SPRITE_IMAGE_FIRE] = Image.createImage("/fire.png");
1160 spriteImages[SPRITE_IMAGE_BOSS1] = Image.createImage("/boss1.png");
1161 spriteImages[SPRITE_IMAGE_ENEMY1] = Image.createImage("/e1.png");
1162 spriteImages[SPRITE_IMAGE_ENEMY2] = Image.createImage("/e2.png");
1163 spriteImages[SPRITE_IMAGE_ENEMY3] = Image.createImage("/e3.png");
1164 spriteImages[SPRITE_IMAGE_EXPLOSION] = Image.createImage("/exp.png");
1165
1166 tileImage = Image.createImage("/t04.png");
1167
1168 imgBackground = Image.createImage("/bg.png");
1169
1170 } catch (Exception e) {};
1171 }
1172
1173 public static GameSprite addExplosion(GameSprite exp_spr)
1174 {
1175 // add explosion
1176 GameSprite exp = new GameSprite(Level.SPRITE_ID_EXPLOSION);
1177 // center the explosion position on the exploded sprite
1178 exp.x = exp_spr.x + exp_spr.w/2 - exp.w/2;
1179 exp.y = exp_spr.y + exp_spr.h/2 - exp.h/2;
1180 exp.vx = exp_spr.vx;
1181 exp.vy = exp_spr.vy;
1182 explosionSprites.addElement(exp);
1183
1184 if (exp_spr.energy > 0)
1185 exp.frameId = GameSprite.EXPLOSION_FRAME_SMOKE_START;
1186
1187 return exp;
1188 }
1189
1190 public void hideNotify()
1191 {
1192 // hideNotify method of Canvas is called on external events, which hide the Canvas
1193 // this method can be used to handle such events (battery warnings, incoming phone calls...)
1194 if (mode == MODE_GAME)
1195 {
1196 mode = MODE_MENU;
1197 menuScreen = MENU_SCR_PAUSE_GAME;
1198 }
1199 }
1200
1201 // public void showNotify()
1202 // {
1203 //
1204 // }
1205
1206
1207 // loads the highscores from RMS - the internal phone database
1208 // also saves default scores, if no scores exist.
1209 public static void loadHighscoreFromRMS()
1210 {
1211 try
1212 {
1213 db = RecordStore.openRecordStore(SCORE_DB_NAME, true);
1214
1215 byte[] score_data = null;
1216 // initialize scores
1217 if (db.getNumRecords() == 0)
1218 {
1219 // add record with initial default scores
1220 score_data = getHighscoreData();
1221 db.addRecord(score_data, 0, score_data.length);
1222 }
1223 // load highscore
1224 score_data = db.getRecord(1);
1225 ByteArrayInputStream in = new ByteArrayInputStream(score_data);
1226 DataInputStream dis = new DataInputStream(in);
1227 for (byte i = 0; i < MAX_SCORES; i++)
1228 {
1229 hiScores[i] = dis.readInt();
1230 hiNames[i] = dis.readUTF();
1231 }
1232 dis.close();
1233 in.close();
1234 db.closeRecordStore();
1235 dis = null;
1236 in = null;
1237 db = null;
1238 // System.gc();
1239 } catch (Exception ex) {
1240 ex.printStackTrace();
1241 }
1242 }
1243
1244 // prepares data in a byte array, ready for saving to a RMS record
1245 public static byte[] getHighscoreData() throws IOException
1246 {
1247 ByteArrayOutputStream out = new ByteArrayOutputStream();
1248 DataOutputStream dos = new DataOutputStream(out);
1249 for (byte i = 0; i < MAX_SCORES; i++)
1250 {
1251 dos.writeInt(hiScores[i]);
1252 dos.writeUTF(hiNames[i]);
1253 }
1254 dos.close();
1255 out.close();
1256
1257 return out.toByteArray();
1258 }
1259
1260 // saves highscores to RMS
1261 public static void saveHighscores()
1262 {
1263 try
1264 {
1265 db = RecordStore.openRecordStore(SCORE_DB_NAME, true);
1266
1267 byte[] data = getHighscoreData();
1268
1269 db.setRecord(1, data, 0, data.length);
1270 db.closeRecordStore();
1271 } catch (Exception ex) {
1272 ex.printStackTrace();
1273 }
1274 }
1275
1276 // returns -1 if not qualified, highscore position otherwise
1277 public static int isQualified(int score)
1278 {
1279 for (byte i = 0; i < MAX_SCORES; i++)
1280 if (hiScores[i] < score)
1281 return i;
1282 return -1;
1283 }
1284
1285 // add a score to the scores array and to the rms
1286 public static void addHighscore(int score, String name)
1287 {
1288 int i = isQualified(score);
1289
1290 // move others down 1 place
1291 for (byte k = (byte) (MAX_SCORES - 1); k > i; k--) {
1292 hiScores[k] = hiScores[k - 1];
1293 hiNames[k] = hiNames[k - 1];
1294 }
1295 // insert score
1296 hiScores[i] = score;
1297 hiNames[i] = name;
1298
1299 saveHighscores();
1300 return;
1301 }
1302
1303 }
1304