motofan logo
10 страниц V < 1 2 3 4 5 > »         
> 

Игровая комната, Обмен опытом при создании игр

Stranger
сообщение 12.10.2006, 20:10 Закрепленное сообщение!


Опытный
***

Группа: Почётные мотофаны
Сообщений: 135
Регистрация: 4.7.2005
Из: Донецк,Украина
Пользователь №: 45 406

Рейтинг: 116



Итак, Вы хотите делать игры... Вам мало бесконечных поисков всевозможных модификаций Вашего телефона, мало непрерывных прошиваний, тюнингов, патчинга, твикинга...Вам нужно что-то еще, у Вас есть голый энтузиазм плюс жгучее желание делать что-то свое, выражая себя, тем самым выделяя свое существование из серой и однообразной повседневности...
Вы что-то слышали про Java, видели даже надпись J2ME на коробке из-под своего мобильного. В глубине души Вы всегда оставались ребенком, Вы играли, а жизнь играла Вами...У Вас всегда под рукой были самые любимые игрушки, теперь же Вы хотите сами их делать...ну что ж...я постараюсь Вам помочь

Что есть мобильные игры? Как они возникли? Какими были сначала?
В 1997 году на телефонах Нокия была впервые предустановлена мобильная игра. Это была всемирно известная "Змейка" (Snake). Несмотря на кажующуюся простоту, невзрачность графики и примитивность игровой логики, эта игра стала предвестником изменения образа мобильного телефона - перерождения его из средства общения в средство развлечения. С тех пор прошло уже немало лет. Утекло немало воды. Новые веяния, мысли и идеи управляют рынком мобильных развлечений. "Ландшафт" мобильных игр быстро меняется. И хотя этот рынок еще жестко не сегментирован, уже четко можно выделить лидеров, на которых равняются все остальные производители(думаю посетители Мотофана знают о ком я говорю :)...)
Правда есть одно "но"... Говорят, что деньги правят миром. Говорят, что сильнее те, у кого их больше. Думаю, вряд ли...Талант, вдохновение, настойчивость и упорство, четкое видение цели и отсутствие боязни сопутствующих по жизни трудностей - вот, что поможет преодолеть все на Вашем пути...Так что дерзайте...Возможно за нами будет будующее новых технологий и новых развлечений...

Что такое Java?
Java зарождался как проект в Sun, цель которого заключалась во внедрении компьютеров в повседневную жизнь...Пришло время, когда технологии и потребности на рынке совпали с исходным назначением языка. Тогда Sun подняла свои наработки и приспособила Java для мобильных телефонов. J2ME стал подмножеством более глобального инструмента Java, который состоит из языка программирования, API и среды выполнения.
Я не буду Вас учить как программировать на J2ME и тем более на Java . Не потому, что не хочу. Нет. Нельзя научить тому, что для самого является источником ежедневных открытий и удивлений. Не верьте тому, кто говорит, что знает все. Все и ничто - суть одно. Без пустого не было бы полного. И наоборот. Поэтому я просто расскажу, что и как делаю я. А решать уж Вам. Согласны ?
Итак, вернусь к тому, с чего начал :) Вы хотите делать мобильные игры... У меня к Вам вопрос: у Вас есть идеи, какую игру написать? Если так, то Вы счастливый человек, ведь идея - это самая важная часть процесса создания игры. Моя карьера J2ME-программиста началась именно так : с яркой и необычной идеи...
Если Вам тяжело, не унывайте. Я Вам помогу. Мы напишем простенький арканоид. Да, да, эта такая простенькая симпатичная игра, где битка отбивает летающий мячик, при этом стараясь разбить им различные кирпичики, набирая очки и жизни. Простенькая в том смысле, что в ней не будет много уровней, бонусов и прочих украшательств. Все это Вы сделаете сами, если захотите. Я же дам основу. Итак, начнем-с...

Цитата
"Основной элемент программы, написанной на языке Java, - это класс(class)."
Точнее и не скажешь. Из всего множества доступных нам знаний и представлений, лежащих в основе постановки задачи нам необходимо выбрать такие непересекающиеся его подмножества, которые бы мы могли классифицировать, как отдельные объекты. Что мы имеем? Есть битка. Она одна и кроме ее координат, размеров и направления перемещения нам от нее ничего и не нужно...Ради одного элемента создавать отдельный класс...правильно не будем... :) Кирпичики(бриксы). Так-так. Подождите. Сколько их будет? Один, два, три...а может четыре. Не путайте людей людей. Их будет много. Сколько захотите. И все они будут разные. Создадим отдельный класс Brick. Думаю, что полями данного класса могут являться следующие элементы:

Код

  private int xPos; // х-я координата отдельного кирпичика
  private int yPos; //у - я координата отдельного кирпичика
  private int typeBrick; // тип кирпичика(содержит бонус или нет)
  private int brickColor; //цвет
  private boolean isCracked; // разбит или нет
  private Bonus bonus = null; // содержащийся внутри бонус

Как Вы видите, среди полей данного класса есть поле Bonus bonus. Еще один класс? Да, и о нем я расскажу немного позже.
Добавим к описанным полям еще немного:

Код
  public static final int RED = 0xff0000; //константа, задающая красный цвет кирпичика
  public static final int GREEN = 0x00ff00; // зеленый
  public static final int BLUE = 0x0000ff; // синий

  public static final int NORMAL = 0; //константа, обозначающая, что кирпичик без бонусов
  public static final int BONUS = 1; // "бонусный" кирпич

  public static final int POINTS_RED = 5; //очки, за красный кирпич
  public static final int POINTS_GREEN = 7; // за зеленый
  public static final int POINTS_BLUE = 10; // за синий

  private Random rand = new Random(); //генератор случайных чисел

Думаю, здесь все понятно. Просто статические константы, обозначающие цвет, тип кирпичика, а также кол-во очков, получаемое за его разбитие. Также имеется генератор случайных чисел Random rand, который мы будем использовать для распределения бонусов между кирпичами. Погодите, а как же конструктор скажете Вы? Секундочку...Вот он:
Код

public Brick(int xPos, int yPos, int typeBrick, int brickColor) {
     this.xPos = xPos;
     this.yPos = yPos;
     this.typeBrick = typeBrick;
     this.brickColor = brickColor;
     if (typeBrick == BONUS) {
       int typeBonus =  Math.abs(rand.nextInt() % 7);
       bonus = new Bonus(xPos, yPos, typeBonus);
     }
  }

Инициализируем нужные поля + если кирпич должен содержать бонус, даем его случайным образом(при этом держим в уме, что я Вам должен рассказать про класс бонусов :))
Также в классе Brick имеются "геттеры и сеттеры". Тут, я думаю, тоже не должно возникнуть проблем:
Код

//Возвращаем х-вую координату кирпича
 public int getPosX(){
     return xPos;
  }
//Возвращаем у-вую координату кирпича
  public int getPosY() {
     return yPos;
  }
//Возвращаем тип кирпича
  public int getTypeBrick(){
     return typeBrick;
  }
//Возвращаем цвет кирпича
  public int getColorBrick(){
     return brickColor;
  }

//Устанавливаем х-вую координату кирпича
public void setPosX(int xPos) {
     this.xPos = xPos;
  }
//Устанавливаем у-вую координату кирпича
  public void setPosY(int yPos) {
     this.yPos = yPos;
  }
//Проверяем разбит ли кирпич
  public boolean isCrackedBrick() {
     return isCracked;
  }
//Разбиваем кирпич
  public void setCrackedBrick(){
     isCracked = true;
  }
//Берем ссылку на бонус у данного кирпича
  public Bonus getBonus() {
     return bonus;
  }

Ну и напоследок метод отрисовки нашего отдельного кирпичика:

Код

public void drawBrick(Graphics g) {
     g.setColor(brickColor);
     g.fillRoundRect(xPos + (Data.SCREEN_WIDTH - Data.FIELD_WIDTH) / 2, yPos , Data.BRICK_WIDTH, Data.BRICK_HEIGHT, 5, 5);
  }

Перейдем наконец к описанию класса Bonus. Как я уже сказал, каждый кирпичик может потенциально содержать бонус. Первая порция полей для данного класса практически идентична полям класса Brick:
Код

  private int xPos;
  private int yPos;
  private int typeBonus;
  private boolean isFlying = false;

Исключением лишь является поле isFlying , отвечающее за то, летит ли бонус или нет.
Есть свои константы и у этого класса(названия говорят сами за себя :)):
Код

  public static final int BONUS_DOUBLE_LENGTH_RACKET = 0;
  public static final int BONUS_HALF_LENGTH_RACKET = 1;
  public static final int BONUS_NORMAL_LENGTH_RACKET = 2;
  public static final int BONUS_DOUBLE_SPEED_BALL = 3;
  public static final int BONUS_HALF_SPEED_BALL = 4;
  public static final int BONUS_NORMAL_SPEED_BALL = 5;
  public static final int BONUS_END_LEVEL_BRICK = 6;

Конструктор + "геттеры и сеттеры" приводить не буду, они практически аналогичны таковым из предыдущего класса. Интерес представляют два метода:

Код
public void drawBonus(Graphics g) {
     int color = 0xffffff;
     String bonusName = "";
     switch (typeBonus) {
        case BONUS_DOUBLE_LENGTH_RACKET:  bonusName = "L"; color = 0xffffff;break;
        case BONUS_HALF_LENGTH_RACKET: bonusName = "H"; color = 0xff00ff;break;
        case BONUS_DOUBLE_SPEED_BALL: bonusName = "F"; color = 0xffff00;break;
        case BONUS_HALF_SPEED_BALL: bonusName = "S"; color = 0x1ffff2;break;
        case BONUS_END_LEVEL_BRICK: bonusName = "E"; color = 0x00ff00;break;
        case BONUS_NORMAL_LENGTH_RACKET: bonusName = "N"; color = 0x20ff10;break;
        case BONUS_NORMAL_SPEED_BALL: bonusName = "N"; color = 0xa0ff22;break;
     }
     g.setColor(color);
     g.drawString(bonusName, xPos + (Data.SCREEN_WIDTH - Data.FIELD_WIDTH) / 2, yPos, g.TOP | g.HCENTER);
  }

Данный метод отвечает за отрисовку бонуса на дисплее телефона. Как Вы видите, изображение бонуса представляет собой буковку, которую Вам нужно будет либо поймать либо пропустить. И всего то. Думаю, понятно, что внеся незначительные изменения в данный класс, можно качественно повысить "красоту" отображения бонусов.
Второй метод:

Код
public void handle() {

     if (++yPos > (Data.FIELD_TOP + Data.FIELD_HEIGHT)) {
        isFlying = false;
     }
  }

Это своеобразный "моторчик" бонуса, заставляющий его "падать" вниз до конца поля.
Если же бонус пролетел, и Вы не успели его поймать, то бонус меняет значение своего поля isFlying на false и исчезает из виду.
При описании этих двух классов Вы не могли не заметить, что в коде встречаются упоминания некого класса Data, из которого мы получаем значения различных статический полей, например, в последнем методе : Data.FIELD_HEIGHT. Что это за класс.
Для меня он был своеобразным хранилищем констант и не более. Вот он:

Код
public class Data {

  public final static int SCREEN_WIDTH = 176; //Размер экрана телефона по ширине(в пикселях)
  public final static int SCREEN_HEIGHT = 204; // Размер экрана телефона по высоте(в пикселях)

  public final static int FIELD_WIDTH = 100; // Ширина игрового поля( в пикселях)
  public final static int FIELD_HEIGHT = 125; // Высота

  public final static int FIELD_TOP = 20; // Смещение верхнего края игрового поля от начала экрана по вертикали(в пикселях)

  public final static int RACKET_WIDTH = 20; //Ширина битки
  public final static int RACKET_SPEED = 2; //Скорость

  public final static int BALL_RADIUS = 3; //Радиус мячика
  public final static int SPACE_BRICK = 3; // Расстояние между кирпичиками
  public final static int BRICK_ARREA_WIDTH = 5; // Кол-во кирпичей в одном ряду
  public final static int BRICK_ARREA_HEIGHT = 5; // Кол-во кирпичей в одном столбце
  public final static int TOP_BRICK_ARREA = FIELD_TOP + 10; // Положение вверхнего края области кирпичей
  public final static int BRICK_WIDTH = 16; //Ширина кирпичика(в пикселях)
  public final static int BRICK_HEIGHT = 10; // Его высота
}

Два основных класса игровых объектов я описал. Осталось еще привести два класса-помощника в создании нашей с Вами игры. Один из них отображает "дедовский" подход
к реализации тригонометрии на телефонах, на которых отсутствует поддержка типа float. Конечно, если телефон поддерживает конфигурацию CLDC 1.1, то можно
работать напрямую с нужными функциями. Но в этом случае могут возникнуть проблемы при портировании игры, так часть телефонов наверняка имеет CLDC 1.0. Писать же
две разных версии кода не в наших с Вами интересах. Поэтому советую воспользоваться моим способом и ознакомиться со следующим классом:

Цитата
public class Util {

   private static final int[] sin = {
        0,  17,  35,  52,  70,  87, 105, 122, 139, 156,
      174, 191, 208, 225, 242, 259, 276, 292, 309, 326,
      342, 358, 375, 391, 407, 423, 438, 454, 469, 485,
      500, 515, 530, 545, 559, 574, 588, 602, 616, 629,
      643, 656, 669, 682, 695, 707, 719, 731, 743, 755,
      766, 777, 788, 799, 809, 819, 829, 839, 848, 857,
      866, 875, 883, 891, 899, 906, 914, 921, 927, 934,
      940, 946, 951, 956, 961, 966, 970, 974, 978, 982,
      985, 988, 990, 993, 995, 996, 998, 999, 999, 1000,
      1000
   };

   private static final int[] tan = {
        0,  17,  35,  52,  70,  87, 105, 123, 141, 158,
      176, 194, 213, 231, 249, 268, 287, 306, 325, 344,
      364, 384, 404, 424, 445, 466, 488, 510, 532, 554,
      577, 601, 625, 649, 675, 700, 727, 754, 781, 810,
      839, 869, 900, 933, 966, 1000, 1036, 1072, 1111, 1150,
      1192, 1235, 1280, 1327, 1376, 1428, 1483, 1540, 1600, 1664,
      1732, 1804, 1881, 1963, 2050, 2145, 2246, 2356, 2475, 2605,
      2747, 2904, 3078, 3271, 3487, 3732, 4011, 4331, 4705, 5145,
      5671, 6314, 7115, 8144, 9514, 11430, 14301, 19081, 28636, 57290
   };

   public static int getSin(int alpha) {
      if (alpha >= 0 && alpha <= 90) {
         return sin[alpha];
      } else if (alpha > 90 && alpha <= 180) {
         return sin[180 - alpha];
      } else if (alpha > 180 && alpha <= 270) {
         return -sin[alpha - 180];
      } else {
         return -sin[360 - alpha];
      }
   }

   public static int getCos(int alpha) {
      if (alpha >= 0 && alpha <= 90) {
         return sin[90 - alpha];
      } else if (alpha > 90 && alpha <= 180) {
         return -sin[alpha - 90];
      } else if (alpha > 180 && alpha <= 270) {
         return -sin[270 - alpha];
      } else {
         return sin[alpha - 270];
      }
   }

   public static int getX(int x, int y, int alpha) {
      return ((x * getCos(alpha) - y * getSin(alpha)) / 1000);
   }

   public static int getY(int x, int y, int alpha) {
      return (x * getSin(alpha) / 1000) + (y * getCos(alpha) / 1000);
   }

   public static int getAlpha(int x, int y) {
      int alpha = 0;
      if (y == 0) {
         alpha = 90;
      } else {
         long tg = 0;
         try {
            tg = Math.abs(x) * 1000 / Math.abs(y);
         } catch (Exception e) {
            tg = 60000;
         }
         if (tg > tan[tan.length - 1]) {
            alpha = 89;
         } else if (tg == 0) {
            alpha = 0;
         } else {
            int i = 1;
            while (!(tan[i] >= tg && tan[i - 1] < tg)) {
               i++;
            }
            alpha = i;
         }
      }
      if (x < 0 && y > 0) {
         alpha = 360 - alpha;
      }
      if (x < 0 && y < 0) {
         alpha += 180;
      }
      if (x > 0 && y < 0) {
         alpha = 180 - alpha;
      }
      if (y == 0) {
         if (x < 0) {
            alpha = 270;
         } else {
            alpha = 90;
         }
      }
      if (x == 0) {
         if (y < 0) {
            alpha = 180;
         }
      }
      return alpha;
   }
}
Как Вы видите, львиную долю содержимого класса составляет два статических массива, названия которых говорят сами за себя. Наверное, Вы догадались, что это
всего лишь числа после запятой из известной Вам еще из школы таблицы. Методы же данного класса просты и основаны на формулах приведения.
Второй класс-помощник позволит выводить на экран телефона созданным собственноручно шрифтом статистику игры:кол-во жизней и очков. Немного слов о том, как это работает.
В Фотошопе я подготовил картинку, на которой были нарисованы цифры 123....90. В принципе, ввести можно все, что угодно(буквы, цифры, знаки...). Далее,
узнаем смещение в пикселях каждого символа от начала картинки и сформируем следующий массив:

Цитата
private final static short[] DATA = {
      0, 8, 18, 28, 38, 48, 58, 68, 78, 88,98
   };

Далее сформируем строку:

Код
private final static String SYMBOLS = "1234567890";

Зададим расстояние между буквами:

Код
private byte SPACE = 4;


Конструктор класса прост:

Код
public GFont() {

     try {
        img = Image.createImage("/digits.png");

     } catch (Exception ex) {

     }
  }

где digits.png - файл с нашими символами.
Два основных метода класса GFont:

Код
public int getWidth(String str) {
     str = str.toUpperCase();
     int w = 0;
     for (int i = 0; i < str.length(); i++) {
        try {
           int pos = SYMBOLS.indexOf(str.charAt(i));
           w += (DATA[pos + 1] - DATA[pos]);
        } catch (Exception ex) {
           w += SPACE;
        }
        w++;
     }
     return w;
  }


  public void drawString(Graphics g, String str, int x, int y, byte align) {

        str = str.toUpperCase();
        int w = getWidth(str);
        if (align == RIGHT) x -= w;
        if (align == CENTER) x -= w >> 1;
        for (int i = 0; i < str.length(); i++) {
           try {
              int pos = SYMBOLS.indexOf(str.charAt(i));
              int _w = (DATA[pos + 1] - DATA[pos]);

              g.setClip(x, y, _w, img.getHeight());
              g.drawImage(img, x - DATA[pos], y, g.TOP | g.LEFT);

              x += _w;
           }
           catch (Exception ex) {
              x += SPACE;
              System.out.println("!!!");
           }
           x++;
        }

     }

Первый из них возвращает длину передаваемой строки в пикселях, а второй(самый главный) - отрисовывает нужную строку на передаваемом Graphics из точки х и у с привязкой align
Таким образом мы рассмотрели вспомогательные классы, которые мы будем использовать в нашем с Вами игре. Перейдем непосредственно к внутренней структуре игры:

Любое приложение под J2ME должно содержать как минимум один класс, который должен расширять(extends) MIDlet. Sun Microsуstems использует суффикс "let" для обозначения раличных типов программ, создаваемых с помощью Java. Вспомните, апплеты, корелеты и теперь мидлеты. Как я уже сказал выше, каждый мидлет должен наследоваться от стандартного класса, который расположен в пакете javax.microedition.midlet. Три метода этого класса крайне важны для разработки мидлета:
- startApp() - метод, выполняющийся при запуске мидлета
- pauseApp() - приостановка мидлета
- destroyApp() - метод, выполняющийся при разрушении мидлета

По сути, эти три метода отражают три состояния, в которых может находиться приложение. Все они составляют так называемый "жизненный цикл" мидлета. Следует также сказать, что в течение жизненного цикла мидлет может входить и выходить в различные состояния несколько раз, но, попав в состояние Destroyed, он уже не сможет вернуться назад.

Наш класс ArcanoidMidlet в соответствии с вышесказанным примет следующий вид:

Код
public class ArcanoidMidlet extends MIDlet {
  static ArcanoidMidlet instance;
  ArcanoidCanvas displayable = null;

  public ArcanoidMidlet() {
     instance = this;
  }

  public void startApp() {
     if (displayable == null) {
        displayable = new ArcanoidCanvas();
        Display.getDisplay(this).setCurrent(displayable);
     }
  }

  public void pauseApp() {
  }

  public void destroyApp(boolean unconditional) {
  }

  public static void quitApp() {
     instance.destroyApp(true);
     instance.notifyDestroyed();
     instance = null;
  }

}


Чтобы двигаться дальше следует уделить внимание еще одной важной концепции разработки мидлетов. Обратите внимание, что при запуске игры(метод startApp() ) вызывается конструкция Display.getDisplay(this).setCurrent(displayable)
Класс Display представляет собой менеджер экрана мобильного устройства. У Вас нет необходимости создавать объект типа данного класса. У мидлета он уже имеется. Единственное, что потребуется от Вас, так это всего лишь получить ссылку на него Display.getDisplay(this) Чтобы закончить описание вышеуказанной конструкции следует рассмотреть еще один класс, которому мы уделим более пристальное внимание чуть позже. Класс javax.microedition.lcdui.Canvas представляет собой абстрактную поверхность для рисования. В связке со специальным механизмом данный класс зачастую становится едва ли не важнейшим для понимания сути разработки игр. Таким образом получив ссылку на Display и вызвав метод setCurrent(displayable), которому в качестве параметра мы передали ссылку на переопределенный нами класс Canvas (еще немножко потерпите и будем рассматривать его), мы установили соответствие между "дисплеем" и "канвасом". Чувствуете? Медленно, но верно мы подходим к самому важному...

Давайте задумаемся, что есть такое игра. Игра - это прежде всего процесс, то есть действия повторяющиеся с течением времени. Появляется закономерный вопрос. Что позволит нам обеспечить эту повторяемость? Какой механизм для этого использовать?
Ответ прост. Использовать потоки. Их основа - класс java.lang.Thread . При этом есть два способа создания потоков. Первый из них - просто унаследоваться от класса потоков и переопределить его метод run() (о нем позже). Второй способ интересней(о нем я уже упоминал выше). Он заключается в реализации интерфейса Runnable. В чем интерес, скажете Вы? Хитрость заключается в том, что унаследовав просто "тред", Вы делаете его единственным родителем Вашего класса. Пойдя же по второму пути Вы оставляете за собой возможность унаследовать Ваш класс от чего угодно, например, от Canvas'а, имея при этом возможность использовать поток. Улавливаете? Это как раз то, что нам нужно... Дабы не обременять Вас теорией скажу, что реализация интерфейса Runnable дает нам возможность переопределять метод run() , который станет "сердцем" и "мотором" нашей игры. Но "мотор нужно завести" скажете Вы. И будете правы.
И для этого нам понадобится все тот же Thread. Думаю, что теперь все необходимые подготовительные работы сделаны, можно переходить к рассмотрению самого главного класса программы.

Итак. Нам необходим класс, который брал бы на себя обязанности отрисовки игровой графики + содержал бы в себе всю игровую логику. Что будем делать? Опишим класс ArcanoidCanvas, причем сделаем это следующим образом:
class ArcanoidCanvas extends Canvas implements Runnable. В контексте вышесказанного, думаю вопросов не возникнет...
Какие будут поля у этого класса? Вот они:

Код

  private boolean threadStopped = false; //Флаг прекращения работы основного потока игры
  private GFont font; //Наш рисованный шрифт
  private Brick[][] brickArrea = new Brick[Data.BRICK_ARREA_HEIGHT][Data.BRICK_ARREA_WIDTH]; //Массив кирпичиков. Уже знакомый нам класс
  private boolean initBrickArrea = false; // Флаг инициализации области кирпичиков
  private int racketX = 0; // Положение по оси Х битки
  private int racketY = 0; //Положение по оси У
  private int racketWidth = 0; // Ширина битки
  private byte racketState = 0; // Состояние ракетки, определяемое константами ниже
  private final static byte RACKET_STOP = 0; //Битка стоит на месте
  private final static byte RACKET_LEFT = 1; //Битка движется влево
  private final static byte RACKET_RIGHT = 2; //Битка движется вправо

  private final static byte RACKET_HALF = 0; // Битка уменьшается вдвое
  private final static byte RACKET_NORMAL = 1; // Нормальный размер битки
  private final static byte RACKET_DOUBLE = 2; //Увеличенный размер битки

  private final static int HIGH_BALL_SPEED = 4; //Повышенная скорость мячика
  private final static int NORMAL_BALL_SPEED = 3; //Нормальная скорость
  private final static int LOW_BALL_SPEED = 2; //Медленный мяч

  private long time = 0;

// Пункты главного меню
  private final String[] menuMain = {
        "New Game",
        "Exit"
  };

//Пункты меню паузы
  private final String[] menuPause = {
        "Resume",
        "Quit"
  };

//Положение в меню
  private int menuPos = 0;


 //Начальный размер битки поставлен в нормальный
  private static byte racketSizeState = RACKET_NORMAL;

  private int ballX = 0; //Иксовая координата мячика
  private int ballY = 0; //Игриковая
  private int ballSpeed = NORMAL_BALL_SPEED; //Скорость мяча. По-умолчанию она нормальная
  private int ballAlpha = 0; // Угол полета мяча
  private int livesNum = 3; //Кол-во жизней
  private int pointsNum = 0; //Кол-во очков

  private byte ballState = 0; //Состояние мяча, определямое константами ниже
  private final static byte BALL_INIT = 0; //Инициализация мяча
  private final static byte BALL_FLY = 1; // Полет мяча

//Состояния игры:
  private final byte STATE_LOGO = 0; //Состояние "Логотип"
  private final byte STATE_MENU = 1; //Состояние "Главное меню"
  private final byte STATE_GAME = 2; //Состояние "Игра"
  private final byte STATE_GAME_WIN = 3; //Состояние "Вы выиграли"
  private final byte STATE_GAME_LOSE = 4; //Состояние "Вы проиграли"
  private final byte STATE_MENU_PAUSE = 5; // Состояние "Меню пауза"

  private byte gameState = STATE_LOGO; // Текущее состояние игры. По-умолчанию - состояние "Логотип"

  private final static int KEY_SOFTKEY1 = -6; //Код левой софт-клавиши(для SE). Для моторол(-21 или иногда  21)
  private final static int KEY_SOFTKEY2 = -7; //Код правой софт-клавиши(для SE). Для моторол (-22 или иногда 22)

  private Random rand = new Random(); //Генератор случайных чисел

Приведу конструктор класса:

Код

     public ArcanoidCanvas() {
     setFullScreenMode(true);
     initData();
     font = new GFont();
     (new Thread(this)).start();
     initBrickArrea = false;
     time = System.currentTimeMillis();
  }

Что в нем делается? Вызов метода setFullScreenMode(true) с передаваемым параметром true говорит, что мы переводим приложение в полноэкранный режим, думаю Вы уже знакомы с эффектом работы данной функции(FullJava помните?). Так. Далее происходит начальная инициализация компонентов игры(рассмотрим ниже). Далее создаем наш шрифт. И самое интересное..."Запускаем мотор" :). После этого говорим, что необходимо сделать инициализацию области кирпичей(initBrickArrea = false;) и засекаем время начала игры. Смотрим на метод initData():
Код
private void initData() {

     racketX = (Data.FIELD_WIDTH / 2);
     racketY = Data.FIELD_HEIGHT;
     racketState = RACKET_STOP;
     racketSizeState = RACKET_NORMAL;
     handleRacketSize();
     ballX = (Data.FIELD_WIDTH / 2);
     ballY = (Data.FIELD_HEIGHT - Data.BALL_RADIUS);
     ballSpeed = NORMAL_BALL_SPEED;
     ballAlpha = 0;
     ballState = BALL_INIT;
     for (int i = 0; i < Data.BRICK_ARREA_HEIGHT; ++i)
        for (int j = 0; j < Data.BRICK_ARREA_WIDTH; ++j) {
           if (initBrickArrea &&
               brickArrea[i][j].getTypeBrick() == Brick.BONUS &&
               brickArrea[i][j].getBonus().isFlying()) {
              brickArrea[i][j].getBonus().setNotFlying();
           }
        }

  }

и вспоминаем, что делали ранее(классы Brick и Bonus)
После того, как "мотор" запущен, начинает выполняться метод run(). Рассмотрим его:
Код

public void run() {
     while (!threadStopped) {

        handle();
        repaint();
        serviceRepaints();
        Thread.currentThread().yield();

        try {
           Thread.currentThread().sleep(25);
        }
        catch (Exception ex) {

        }
     }
     ArcanoidMidlet.quitApp();
  }

Это "классическая" реализация игры. Наверное, в 97 случаях из 100 все игры, которые существуют на рынке имеют похожую реализацию данного метода. Конечно, бывают более совершенные реализации. Но про них я расскажу в своих последующих статьях. "Классика" есть "классика". Приложение закончит свою работу с прекращением работы метода run() . Но нам нужен цикл. Для этого мы и помещаем все в цикл while (!threadStopped).Теперь, чтобы прекратить игру нужно только поставить флаг threadStopped в true. Внутри цикла вызываются основные методы игры: handle() - игровая логика, "парочка" repaint() и serviceRepaints(), отвечающие за перерисовку графики, Thread.currentThread().yield() - "говорим" текущему потоку временно прекратить свою работу, чтобы дать отработать другим потокам. Нам это нужно, чтобы игра своевременно реагировала на нажатия кнопок на телефоне. Этому же служит и конструкция:
Код

        try {
           Thread.currentThread().sleep(25);
        }
        catch (Exception ex) {
        }

где мы "говорим" текущему потоку замереть на 0,25 с.
Последняя строка в нашем "моторе" ArcanoidMidlet.quitApp(), в которую мы попадем только, когда захотим закончить игру. Вспомните, класс нашего мидлета. Вспомнили? А помните, я рассказывал про состояния мидлета. После этого мидлет перейдет в состояние destroyed.
Я не вижу смысла приводить полностью весь код данного класса, так как к статье я прилагаю исходники данного проекта, расскажу только вкратце о главном. Метод handle() содержит, как я уже говорил выше, игровую логику приложения. Идет проверка столкновения мяча со стенками, с биткой, происходит проверка того, летит ли бонус, словлен ли он игроком. Вызывается метод checkCollision() для проверки столкновения мяча с кирпичами. Также при столкновении мяча с биткой происходит расчет угла отражения мяча в зависимости от того, какой частью битки был отбит мячик. Более детальный обзор этих методов я смогу дать, отвечая на Ваши вопросы. Хочу напоследок остановиться на двух важных методах, присутствующих в нашем классе. Помните метод repaint()? Вызов данного метода вызывает метод paint() нашего Canvas'a. Данный метод в зависимости от состояний игры(меню, игра, лого...) отрисовывает нужную графику. Методы эти несложные, думаю разберетесь. Осталось рассмотреть еще метод keyPressed(int keyCode), отвечающий за отработку нажатия клавиш. Вернее сказать, таких методов 3. "Кипресд" срабатывает, когда мы нажали и держим клавишу, keyReleased(int keyCode), когда мы отпускаем клавишу, keyRepeated(int keyCode) - при повторяющихся нажатиях. Как Вы видите, данные методы получают в качестве параметра код клавиши. Помните, в самом начале среди полей класса мы задавали собственноручно коды для софт-клавиш? В приложении каждая клавиша имеет свой код, и по нему можно отследить нажатие именно на нее. Но представляете, как это утомительно, хранить коды всех клавиш. Поэтому для упрощения работы с клавишами есть более простой механизм. Пример из нашей программы:
Код

             int action = 0;
              try {
                 action = getGameAction(keyCode);
              }
              catch (Exception ex) {

              }

              switch (action) {
                 case UP:
                 ....
                  break;
                 case DOWN:
                 ....
                 break;
                 case FIRE:
                 ....
                    break;
              }
Смысл этого участка кода заключается в том, что клавишам по их коду ставится в соответствие action и уже по нему, используя константы "Канваса" (UP, DOWN, FIRE) можно совершать нужные нам действия. Прелесть данного метода заключается в том, что такой код будет работать универсально на любом телефоне и Вам не нужно знать какие коды соответствуют нажатым клавишам. Единственное, что софт-клавиши обработать таким образом не получится. Поэтому придеться хранить отдельно их коды. Метод keyRepeated(int keycode) в нашей игре не используется, однако используется метод keyReleased(int keyCode), который я использую для остановки битки(racketState = RACKET_STOP)

Как Вы видите, 3 основных метода игры, а именно handle(), paint(Graphics g), keyPressed(int code) работают в строгой зависимости от того состояния, в котором находится игра. Поэтому такую реализацию игры иногда еще называют "state machine"(машиной состояний). Существуют различные мнения по поводу такого подхода к созданию игр. Одни считают, что это больше похоже на процедурное программирование и говорят, что тем самым унижаются все ООП достоинства языка Java, другие говорят, что мы имеем дело все-таки с J2ME и ресурсы у нас ограничены. Я думаю, что правы все. Я видел различные реализации игр. Я видел, как человек переделывал игры под стейт-машину из-за слишком большого кол-ва классов и неработоспособности приложения на некоторых телефонах, видел всю несостоятельность попытки автоматизировать разработку и портирование игр под огромный модельный ряд телефонов только из-за того, что некоторые люди не понимают, что
Цитата
"само никогда ничего не делается"
, видел и , надеюсь, увижу многое...
В заключении хотелось бы сказать, что несмотря на свою кажующуюся простоту и примитивность, данная сфера разработок программного обеспечения имеет огромное будующее, так как число обладателей мобильных телефонов растет, как и ростут потребности людей в удовлетворении своих ежедневных желаний развлечения и удовольствия. Не за горами те дни, когда возможности мобильных устройств станут соизмеримы с современными ПК, когда мобильные приложения выйдут на новый более качественный виток своего развития. Ежегодный приток денежных масс, повышенный интерес, креативность и изменчивость направлений - вот основные аргументы, позволяющие с уверенностью говорить, что мобильные развлечения себя еще покажут. На что и надеется Ваш скромный слуга :)

P.S.
Изначально статья задумывалась как FAQ для начинающих, дабы помочь им в написании своей первой простенькой игры. Однако по ходу работы автор незаметным для себя способом несколько отошел от чисто технической стороны и пристал к берегу философских наблюдений за развитием интересующей его сферы деятельности, в связи с чем несколько сократились пояснительные элементы для основного класса примера. Однако автор искренне уверен, что прилагаемые к статье исходники смогут помочь всем жаждущим в освоении азов программирования мидлетов. В чем он собирается им всячески помогать.

P.P.S.
Хотелось бы сказать, что данный мидлет "заточен" под E398. Однако внеся минимальные изменения в класс Data и поменяв константы софт-клавиш(если Вы портируете игру под телефоны, отличные от Моторола), Вы без труда сможете адаптировать приложение под любой другой модельный ряд телефонов.


Прикрепленный файл Arcanoid.rar   ( 37.53 килобайт ) Кол-во скачиваний: 776
Прикрепленное изображение
Прикрепленный файл Arcanoid.rar   ( 37.53 килобайт ) Кол-во скачиваний: 776
Юзер вышелВ друзьяВизиткаП/Я
К началу страницы
+Ответить
JenFA
сообщение 28.10.2004, 21:29


Ветеран
*****

Группа: Пользователи
Сообщений: 538
Регистрация: 10.7.2004
Из: Одесса
Пользователь №: 7 633
Модель телефона: C650
Прошивка: 31R

Рейтинг: 134.5



Agent 707

Тут как раз многоугольники произвольные. А код дал какой был. Выдрал из библиотечки какой-то... Другого нет пока. Нету функции... а жаль...

max.wiz

Ой тормозить будет... но, как говорится, другой альтернатмвы нет (пока нет)
Юзер вышелВ друзьяВизиткаП/Я
К началу страницы
+Ответить
SVK
сообщение 29.10.2004, 6:08


Опытный
***

Группа: Пользователи
Сообщений: 102
Регистрация: 17.3.2004
Пользователь №: 2 206

Рейтинг: 1.5



Цитата(JenFA @ 28.10.2004 - 21:29)
max.wiz

Ой тормозить будет... но, как говорится, другой альтернатмвы нет (пока нет)

Гм... так ведь можно декомпильнуть прототип вашей игры и посмотреть как там всё реализовано.
Описание модели игры есть? холмы/повороты на дороге будут, структура пейзажа? Если ориентироваться на MotoGP, то трассу можно, наверное (чисто умозаключительная версия), строить из наборов широких, но невысоких спрайтов.
Юзер вышелВ друзьяВизиткаП/Я
К началу страницы
+Ответить
JenFA
сообщение 29.10.2004, 7:51


Ветеран
*****

Группа: Пользователи
Сообщений: 538
Регистрация: 10.7.2004
Из: Одесса
Пользователь №: 7 633
Модель телефона: C650
Прошивка: 31R

Рейтинг: 134.5



SVK

1. Ну ведь заливают как-то же люди многоугольники! Вот в Directdraw тоже вроде как функции нет, а заливают...
2. Какой игры? Если демки Agent 707, то там сырцы в архиве...
Юзер вышелВ друзьяВизиткаП/Я
К началу страницы
+Ответить
SVK
сообщение 29.10.2004, 10:58


Опытный
***

Группа: Пользователи
Сообщений: 102
Регистрация: 17.3.2004
Пользователь №: 2 206

Рейтинг: 1.5



Цитата(JenFA @ 29.10.2004 - 07:51)
1. Ну ведь заливают как-то же люди многоугольники! Вот в Directdraw тоже вроде как функции нет, а заливают...

Всё упирается в производительность железа и доступный функционал. Вон SUN MIDP2.0 можно уже и триугольники заливать.
P.S. DirectDraw нам тут не сильно поможет :?( А гонок для J2ME можно наверное пару штук наковырять и подсмотреть как там всё это происходит.
Юзер вышелВ друзьяВизиткаП/Я
К началу страницы
+Ответить
JenFA
сообщение 29.10.2004, 11:25


Ветеран
*****

Группа: Пользователи
Сообщений: 538
Регистрация: 10.7.2004
Из: Одесса
Пользователь №: 7 633
Модель телефона: C650
Прошивка: 31R

Рейтинг: 134.5



SVK

Насёт DD - я имею ввиду, что есть какие-то алгоритмы... вот в MotoGP они закругления дорог рисуют... и спрайтов с ними нет.
Юзер вышелВ друзьяВизиткаП/Я
К началу страницы
+Ответить
SVK
сообщение 29.10.2004, 11:41


Опытный
***

Группа: Пользователи
Сообщений: 102
Регистрация: 17.3.2004
Пользователь №: 2 206

Рейтинг: 1.5



Цитата(JenFA @ 29.10.2004 - 11:25)
Насёт DD - я имею ввиду, что есть какие-то алгоритмы...
Я не сомневаюсь :)
Цитата(JenFA @ 29.10.2004 - 11:25)
вот в MotoGP они закругления дорог рисуют... и спрайтов с ними нет.
Если ты знаешь каким образом они рисуют дорогу, то не вопрос ;)
Юзер вышелВ друзьяВизиткаП/Я
К началу страницы
+Ответить
JenFA
сообщение 29.10.2004, 12:02


Ветеран
*****

Группа: Пользователи
Сообщений: 538
Регистрация: 10.7.2004
Из: Одесса
Пользователь №: 7 633
Модель телефона: C650
Прошивка: 31R

Рейтинг: 134.5



SVK

Не знаю, может декомпайлить попробуешь?
Юзер вышелВ друзьяВизиткаП/Я
К началу страницы
+Ответить
Agent 707
сообщение 30.10.2004, 1:02


Специальный агент
****

Группа: Почётные мотофаны
Сообщений: 251
Регистрация: 8.8.2003
Из: Россия, г. Волгоград
Пользователь №: 405
Модель телефона: Motorola L7e

Рейтинг: 48



Я написал алгоритм заливки, правда работает попиксельно, что не очень-то хорошо, так что попробую еще довести до заливки линиями на той же основе.
Пока алгоритм писал на Pascal, потому что не знаю как в яве получить состояние пиксела. Напримр в паскале есть функция GetPixel(x,y), которая возвращяет цвет пиксела, находящегося по указанным координатам. А как с этим в J2ME? И еще, как поставить один пиксел (не линию, а только пиксел)? Также жду других предложений по реализации этой функции...
Юзер вышелВ друзьяВизиткаП/Я
К началу страницы
+Ответить
Agent 707
сообщение 31.10.2004, 16:05


Специальный агент
****

Группа: Почётные мотофаны
Сообщений: 251
Регистрация: 8.8.2003
Из: Россия, г. Волгоград
Пользователь №: 405
Модель телефона: Motorola L7e

Рейтинг: 48



Еще вопрос - можно ли рисовать "поверх" картинки? Т.е. рисовать Canvas'ом поверх Image'а, но так, чтобы часть картинки была видна на экране, а поверх другой был рисунок Canvas'ом. Это нужно чтобы город мог "выезжать" из-за горизонта. Если и это нельзя, придется серьезно менять планы... и делать дорогу спрайтовой, из-за чего, ясное дело, эффект уходящей вдаль трассы будет хуже.
Юзер вышелВ друзьяВизиткаП/Я
К началу страницы
+Ответить
JenFA
сообщение 31.10.2004, 21:53


Ветеран
*****

Группа: Пользователи
Сообщений: 538
Регистрация: 10.7.2004
Из: Одесса
Пользователь №: 7 633
Модель телефона: C650
Прошивка: 31R

Рейтинг: 134.5



Agent 707

Рисуешь на Canvas картинку drawImage-м, а потом чё-то ещё рисуешь - получается поверх ;)
Юзер вышелВ друзьяВизиткаП/Я
К началу страницы
+Ответить
Игровая комната, Обмен опытом при создании игр · Разработка Java-игр · Forum
 

10 страниц V < 1 2 3 4 5 > » 
Ответ в темуСоздание новой темы
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 



Текстовая версия Сейчас: 19.3.2024, 5:48

Форум живёт: