[ libGDX ] Пишем полноценную игру под Android. Часть 2

от автора

Здравствуйте! Не прошло суток с момента публикации первой части статьи, а я не могу спать, так как есть незаконченное дело и нужно дописать статью. Приступим.


Оговорюсь еще раз. Я шибкий не знаток Java и поэтому следующий далее код, может смутить многих, но игру я написал меньше, чем за неделю и работал скорее на результат, чем на красоту и порядочность кода. Надеюсь, в комментариях найдется тот, кто поможет сделать код и структуру проекта, если не совершенными, то хотя бы привести к хорошему виду и дать возможность мне и остальным стать более хорошими программистами. Ладно, хватит лирики, продолжим наш «хардкор».
Создадим новый package и назовем его objects. В нем создадим класс фона, а в него добавим следующий код:

Файл BackgroundActor.java

package ru.habrahabr.songs_of_the_space.objects;  import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.Sprite; import com.badlogic.gdx.scenes.scene2d.Actor;  public class BackgroundActor extends Actor {     private Texture backgroundTexture;     private Sprite backgroundSprite;      public BackgroundActor() {         backgroundTexture = new Texture("images/sky.jpg");         backgroundSprite = new Sprite(backgroundTexture);         backgroundSprite.setSize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());     }      @Override     public void draw(Batch batch, float alpha) {         backgroundSprite.draw(batch);     } } 

Ничего сложного. Это «актер», который устанавливается по размеру экрана пользователя и делает нашу игру более похожей на звездное небо. Примерно так это должно выглядеть:

Главный экран игры

image

Теперь добавим его в MyGame.java и сделаем его доступным извне, для того, чтобы не создавать его на каждом следующем экране. Это избавит нас от мерцания.

Файл MyGame.java

    // Перед методом create()     public BackgroundActor background;      @Override     public void create() {         ...                  background = new BackgroundActor();         background.setPosition(0, 0);                  ...     } 

Далее, мы должны в каждом новой экране добавлять его на сцену:

stage.addActor(game.background); 

Теперь, также в пакете objects создадим класс ноты. Он будет хранить все наши ноты в нужной нам последовательности.

Файл Note.java

package ru.habrahabr.songs_of_the_space.objects;  public class Note {     private String note;     private float delay;     private Star star;      // Устанавливаем ноты. Ноты будем брать из xml файла уровня.     public void setNote(String note) {         this.note = note;     }      public String getNote() {         return this.note;     }      // Устанавливаем задержку для ноты, чтобы можно было создавать мелодии разной сложности     public void setDelay(String delay) {         this.delay = Float.parseFloat(delay);     }      public float getDelay() {         return this.delay;     }      // Наша красавица -- звезда     public void setStar(Star star) {         this.star = star;     }      public Star getStar() {         return this.star;     } } 

Теперь, когда мы создали ноту, нам нужно создать звезду, которая будет нашим основным актером в нашей космической сцене. Она будет мерцать и петь свою чудную мелодию для будущих пользователей.
Перед тем, как продолжить немного поясню, зачем нам нужен отдельный класс для ноты и для звезды. Мелодия может повторять свои ноты, а каждая звезда должна быть в единственном экземпляре. Когда я только продумывал идею игры, я как раз хранил каждую ноту внутри звезды. В итоге, либо мелодия была слишком простой, либо звезд на небе становилось слишком много и было сложно пройти уровень даже с восемью повторяющимися нотами.
Итак, создаем звезду.

Файл Star.java

package ru.sayakhov.songs_of_the_space.objects;  import com.badlogic.gdx.Gdx; import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture.TextureFilter; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.Sprite; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.Touchable; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;  public class Star extends Actor {          // Звук, если пользователь ошибся     private Sound sound, wrong;      // Ноты в строковом представлении     private String note;      // Изображение звезды     private Sprite img;     private Texture img_texture;          // Наш уровень. Он будет говорить, где должна находиться звезда     private Level level;          public Star(String str_img, String str_sound) {         img_texture = new Texture("images/stars/" + str_img + ".png");         img_texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);         img = new Sprite(img_texture);          // Это я сделал для того, чтобы размер звезды менялся в зависимости от экрана пользователя          img.setSize(Gdx.graphics.getHeight() * 15 / 100, Gdx.graphics.getHeight() * 15 / 100);         this.note = str_sound;         this.sound = Gdx.audio.newSound(Gdx.files.internal("sounds/bells/" + str_sound + ".mp3"));         this.wrong = Gdx.audio.newSound(Gdx.files.internal("sounds/bells/wrong.mp3"));          // Слушает события касания пользователя и играет соответствующую ноту, а также создает эффект мерцания за счет увеличения звезды в размерах         addListener(new ClickListener() {             @Override             public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {                 img.setScale(1.2f);                 if (note.equals(level.getCurrentNoteStr())) {                     level.setCurrentNote();                     Gdx.input.vibrate(25); // Дадим пользователю понять, что он нажал немного вибрируя в момент касания                     getSound().play();                 } else {                      // Если юзер ошибся, то начинаем сначала. Проигрываем первые четыре ноты и играем их. А также сильнее вибрируем, чтобы оповестить его об ошибке.                      level.setCurrentNote(0);                     level.setEndNote(true);                     level.setPlayMusic();                     getWrongSound().play();                     Gdx.input.vibrate(80);                 }                 return true;             }                          @Override             public void touchUp(InputEvent event, float x, float y, int pointer, int button) {                 img.setScale(1.0f); // Как только пользователь отпустил нашу звезду, делаем ее размер таким же, каким он был             }         });         setTouchable(Touchable.enabled); // Делаем нашу звезду активной для касания     }          public void setLevel(Level level) {         this.level = level;     }           // Устанавливаем позицию изображения равной позиции актера, и делаем размеры актера равными размеру звезды     @Override     public void setBounds(float x, float y, float width, float height) {         super.setBounds(x, y, this.img.getWidth(), this.img.getHeight());         this.img.setPosition(x, y);     }          // В каждый момент исполнения, немного крутим нашу звезду. Пусть потанцует.     @Override     public void act(float delta) {         img.rotate(0.05f);     }          // Рисуем звезду на сцене     @Override     public void draw(Batch batch, float alpha) {         this.img.draw(batch);     }          public Sound getSound() {         return this.sound;     }          public Sound getWrongSound() {         return this.wrong;     }          public String getNote() {         return this.note;     }          public Sprite getImg() {         return this.img;     } } 

Теперь создадим наш класс уровня. Он будет отвечать за создания всех актрис и актеров, а также играть мелодию и поздравлять в победой. Я добавил его в пакет objects, но он лучше подходит как менеджер, поэтому можете перенести его туда самостоятельно.

Файл Level.java

package ru.habrahabr.songs_of_the_space.objects;  import java.util.HashMap; import java.util.Map;  import com.badlogic.gdx.Gdx; import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.scenes.scene2d.Touchable; import com.badlogic.gdx.utils.Array;  public class Level {          private XMLparse xml_parse;     private Array<Note> notes = new Array<Note>();     private Array<Star> stars = new Array<Star>();     private Map<String, Array<String>> starsPos = new HashMap<String, Array<String>>();          private int currentNote;     private int endNote;          private float delay;     private boolean playMusic;          private boolean win;          private final Sound winner = Gdx.audio.newSound(Gdx.files.internal("sounds/win.mp3")); // Победный звук аплодисментов          public Level(String level) {         xml_parse = new XMLparse();         Array<Star> xml_stars = xml_parse.XMLparseStars(); // парсим звезды из всего списка имеющихся         notes = xml_parse.XMLparseNotes(level); // парсим ноты для уровня         starsPos = xml_parse.getPos(level); // позиции звезд в текущем уровне         endNote = 3;         delay = 0;         this.win = false;                  setPlayMusic();                  for (Note n : this.notes) {             for (Star s : xml_stars) {                 if (n.getNote().equals(s.getNote()) && !this.stars.contains(s, true)) { // Поскольку в одном xml у нас хранятся все возможные варианты звезд, этот код отсеит лишние                     this.stars.add(s);                 }                 if (n.getNote().equals(s.getNote())) n.setStar(s); // А здесь мы устанавливаем для каждой ноты свою звезду             }         }          for (Star s : this.stars) {             s.setLevel(this);             s.setBounds(                  // Это нужно для того, чтобы позицию звезды можно было описать в процентом от размера экрана пользователя отношении (так как скопления наших звезд будут стараться походить на настоящие созвездия реального космоса)                  Gdx.graphics.getWidth() * Float.parseFloat(starsPos.get(s.getNote()).get(0)) / 100,                 Gdx.graphics.getHeight() * Float.parseFloat(starsPos.get(s.getNote()).get(1)) / 100 - s.getImg().getHeight() / 2,                 s.getImg().getWidth(),                 s.getImg().getHeight()             );         }     }          public boolean isWin() {         return this.win;     }      // Устанавливаем последнюю ноту          public void setEndNote() {         if (this.endNote < this.notes.size - 1) {             this.endNote += 4;         }     }          // Переопределяем метод для того, чтобы в случае, когда пользователь ошибся, сделать последней четвертую ноту.     // Можно было обойтись и одним методом, но мне так понравилось больше. Переопределяй! Властвуй!      public void setEndNote(boolean begin) {         if (begin) {             this.endNote = 3;         }     }          public void setCurrentNote(int note) {         this.currentNote = note;     }      // Устанавливаем текущую ноту          public void setCurrentNote() {         if (this.currentNote < this.notes.size - 1) {             this.currentNote++;             if (currentNote - 1 == endNote) {                 currentNote = 0;                 setEndNote(); // Увеличиваем значение на 4 для последней ноты                 setPlayMusic(); // Играем мелодию с большим количеством нот             }         } else {              // Если пользователь отыграл все ноты, играем победные аплодисменты              this.endNote = notes.size - 1;             this.currentNote = 0;             this.win = true;             this.winner.play();         }     }          public int getCurrentNote() {         return this.currentNote;     }          public String getCurrentNoteStr() {         return this.notes.get(this.currentNote).getNote();     }          public Array<Note> getNotes() {         return this.notes;     }          public Array<Star> getStars() {         return this.stars;     }               public void setPlayMusic() {         if (playMusic) {             playMusic = false;         } else {             playMusic = true;         }     }          // Играем наши ноты для пользователя      public void playStars() {         if (playMusic) {             for (Star s : stars) {                 s.setTouchable(Touchable.disabled); // Не даем пользователю трогать наши звезды, пока играет мелодия             }             if (getCurrentNote() < notes.size) {                 if (getCurrentNote() <= endNote) {                     Note note = notes.get(getCurrentNote());                                          delay += note.getDelay(); // delay позволяет создавать задержку по времени между проигрыванием нот                                          if (delay >= 0.9f) note.getStar().getImg().setScale(1.2f); // Увеличиваем активную в данный момент звезду для того, чтобы создать эффект мерцания                                          if (delay >= 1.0f) {                         delay = 0;                         setCurrentNote(currentNote + 1);                         note.getStar().getSound().play();                         note.getStar().getImg().setScale(1f);                     }                 } else {                     setPlayMusic();                     setCurrentNote(0);                 }             } else {                 delay = 0;                 setCurrentNote(0);                 setPlayMusic();             }         } else {             for (Star s : stars) {                 s.setTouchable(Touchable.enabled); // Делаем все наши звезды активными для касания             }         }     } } 

Надеюсь, все понятно. Старался максимально комментировать код. Единственное, что может вызвать вопросы — это delay. Поясню немного. Метод playStars() будет вызываться в методе render() класса PlayScreen.java. Поскольку, он выполняется в потоке, каждый раз при совпадении всех условий, delay будет увеличиваться на заданное количество. Таким образом, будет имитироваться задержка в игре нот. Это лучше увидеть в коде. Давайте, наконец наполним наш класс PlayScreen.java. Поскольку, там много кода, я решил его спрятать под спойлер.

Файл PlayScreen.java

package ru.habrahabr.songs_of_the_space.managers;  import ru.habrahabr.songs_of_the_space.MyGame; import ru.habrahabr.songs_of_the_space.objects.GamePreferences; import ru.habrahabr.songs_of_the_space.objects.Level; import ru.habrahabr.songs_of_the_space.objects.PlayStage; import ru.habrahabr.songs_of_the_space.objects.PlayStage.OnHardKeyListener; import ru.habrahabr.songs_of_the_space.objects.Star;  import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.Screen; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.Touchable; import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle; import com.badlogic.gdx.scenes.scene2d.ui.Skin; import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.scenes.scene2d.ui.TextButton; import com.badlogic.gdx.scenes.scene2d.ui.TextButton.TextButtonStyle; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.viewport.ScreenViewport;  public class PlayScreen implements Screen {          final MyGame game;          private GamePreferences pref;          private Level level;     private String sL, nL;     private Array<Star> stars;      private PlayStage stage;     private Table table, table2;          public PlayScreen(final MyGame gam, String strLevel, String strNextLevel) {         game = gam;         this.sL = strLevel;         this.nL = strNextLevel;                  stage = new PlayStage(new ScreenViewport());                  stage.addActor(game.background); // Добавляем фон                  pref = new GamePreferences();                  level = new Level(strLevel);         stars = level.getStars();                  level.setCurrentNote(0);                  for (final Star s : stars) {             stage.addActor(s); // Добавляем всех актрис (звезды) на сцену         }                  LabelStyle labelStyle = new LabelStyle();         labelStyle.font = game.font;                  // Skin для кнопок, которые показываются в случае победы пользователя          Skin skin = new Skin();         TextureAtlas buttonAtlas = new TextureAtlas(Gdx.files.internal("images/game/images.pack"));         skin.addRegions(buttonAtlas);         TextButtonStyle textButtonStyle = new TextButtonStyle();         textButtonStyle.font = game.font;         textButtonStyle.up = skin.getDrawable("button-up");         textButtonStyle.down = skin.getDrawable("button-down");         textButtonStyle.checked = skin.getDrawable("button-up");                  // Для всех кнопок лучше создать таблицу, так как она хорошо справляется с варавниванием          table = new Table();         table.padTop(20);         table.center().top();         table.setFillParent(true);          // label для показа названия созвездия                  Label label = new Label(game.langStr.get("Constellation"), labelStyle);         table.add(label);         table.row().padBottom(30);         label = new Label(game.langStr.get("level_" + strLevel), labelStyle);         table.add(label);                  table.setVisible(false);                  stage.addActor(table);                  table2 = new Table();         table2.center().bottom();         table2.setFillParent(true);         table2.row().colspan(2).padBottom(30);         label = new Label(game.langStr.get("YouWin"), labelStyle);         table2.add(label).bottom();         table2.row().padBottom(20);         TextButton button = new TextButton(game.langStr.get("Again"), textButtonStyle);          // Нужно не забыть заставить кнопки прослушивания событих клика (касания)                  // Эта кнопка, после нажатия, запустит уровень сначала                  button.addListener(new ClickListener() {             @Override             public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {                 Gdx.input.vibrate(20);                 return true;             };             @Override             public void touchUp(InputEvent event, float x, float y, int pointer, int button) {                 game.setScreen(new PlayScreen(game, sL, nL));                 dispose();             };         });         table2.add(button);          // А эта перенесет пользователя обратно на экран выбора уровня          button = new TextButton(game.langStr.get("Levels"), textButtonStyle);         button.addListener(new ClickListener() {             @Override             public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {                 Gdx.input.vibrate(20);                 return true;             };             @Override             public void touchUp(InputEvent event, float x, float y, int pointer, int button) {                 game.setScreen(new LevelScreen(game));                 dispose();             };         });         table2.add(button);         table2.setVisible(false);                  stage.addActor(table2);                  Gdx.input.setInputProcessor(stage);         Gdx.input.setCatchBackKey(true);         stage.setHardKeyListener(new OnHardKeyListener() {                       @Override             public void onHardKey(int keyCode, int state) {                 if (keyCode == Keys.BACK && state == 1){                     game.setScreen(new LevelScreen(game));                     }                    }         });     }      @Override     public void render(float delta) {          // Очистка экрана в каждый момент выполнения потока              Gdx.gl.glClearColor(0, 0, 0, 1);         Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);                  // Рисуем сцену и вызываем метод act() для дополнительных действий актеров, описанных в одноименном методе каждого (в нашем случае, это вращение звезд)                  stage.act(delta);         stage.draw();                  level.playStars();                  // Если пользователь выиграл, то показываем ему все наши кнопки, label'ы и прочее          if (level.isWin()) {             table.setVisible(true);             table2.setVisible(true);             pref.setLevel(nL); // Это для настроек игры. Объяснения ниже.             for (Star s : stars) {                 s.setTouchable(Touchable.disabled); // Делаем все звезды неактивными для касания после победы пользователя             }         }     }      @Override     public void resize(int width, int height) {}      @Override     public void show() {}      @Override     public void hide() {}      @Override     public void pause() {}      @Override     public void resume() {}      // На забываем уничтожить сцену и объект класса MyGame      @Override     public void dispose() {         stage.dispose();         game.dispose();     } } 

Наверное, код вызвал несколько вопросов, так как в нем можно заметить новый класс GamePreferences.java. Этот класс позволит нам хранить все настройки игры в удобном формате. Для Android приложения будет создан, так называемый «SharedPreferences». Подробнее здесь. В данном случае, в нем мы будем хранить пройденные пользователем уровни.
Ну что? Давайте теперь создадим и наполним его.

Файл GamePreferences.java

package ru.habrahabr.songs_of_the_space.objects;  import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Preferences;  public class GamePreferences {     private Preferences pref;          private static final String PREFS_NAME = "SONGS_OF_THE_SPACE";     private static final String PREF_LEVEL = "LEVEL_";          public GamePreferences() {         pref = Gdx.app.getPreferences(PREFS_NAME);     }          public boolean getLevel(String level) {         pref.putBoolean(PREF_LEVEL + 1, true);         pref.flush();         return pref.getBoolean(PREF_LEVEL + level, false);     }       public void setLevel(String level) {         pref.putBoolean(PREF_LEVEL + level, true);         pref.flush();     } } 

В нем нет ничего сложного. Не буду дублировать документацию, ссылку на нее я дал ниже. Теперь нам нужно немного обновить наш класс XMLparse.java. Так, мы еще не научили нашу парсить звезды и ноты. Сделаем это.

Файл XMLparse.java

package ru.habrahabr.songs_of_the_space.objects;  import java.io.IOException; import java.util.HashMap; import java.util.Map;  import com.badlogic.gdx.Application.ApplicationType; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.XmlReader; import com.badlogic.gdx.utils.XmlReader.Element;  public class XMLparse {          private Array<Star> stars = new Array<Star>();     private Array<Note> notes = new Array<Note>();     private Map<String, Array<String>> starsPos = new HashMap<String, Array<String>>();      // В этом методе мы будем парсить наши переводы. А вы как думали? Мы делаем многоязычную игру!          public HashMap<String, String> XMLparseLangs(String lang) {         HashMap<String, String> langs = new HashMap<String, String>();         try {             Element root = new XmlReader().parse(Gdx.files.internal("xml/langs.xml"));             Array<Element> xml_langs = root.getChildrenByName("lang");                          for (Element el : xml_langs) {                 if (el.getAttribute("key").equals(lang)) {                     Array<Element> xml_strings = el.getChildrenByName("string");                     for (Element e : xml_strings) {                         langs.put(e.getAttribute("key"), e.getText());                     }                 } else if (el.getAttribute("key").equals("en")) {                     Array<Element> xml_strings = el.getChildrenByName("string");                     for (Element e : xml_strings) {                         langs.put(e.getAttribute("key"), e.getText());                     }                 }             }         } catch (IOException e) {             e.printStackTrace();         }         return langs;     }      // В этом методе парсим звезды          public Array<Star> XMLparseStars() {         try {             Element root = new XmlReader().parse(Gdx.files.internal("xml/stars.xml"));             Array<Element> xml_stars = root.getChildrenByName("star");                          for (Element el : xml_stars) {                 Star star = new Star(                     el.getAttribute("files"),                     el.getAttribute("files")                 );                                  stars.add(star);             }         } catch (IOException e) {             e.printStackTrace();         }         return this.stars;     }      // В этом парсим уровни          public Array<String> XMLparseLevels() {         Array<String> levels = new Array<String>();         Array<Integer> int_levels = new Array<Integer>();                  FileHandle dirHandle;         if (Gdx.app.getType() == ApplicationType.Android) {             dirHandle = Gdx.files.internal("xml/levels");         } else {                          // Это хак, так как libGDX почему-то не хотел видеть этот файл при тестировании Desktop приложения              dirHandle = Gdx.files.internal(System.getProperty("user.dir") + "/assets/xml/levels");         }         for (FileHandle entry : dirHandle.list()) {             levels.add(entry.name().split(".xml")[0]);         }                  for (int i = 0; i < levels.size; i++) {             int_levels.add(Integer.parseInt(levels.get(i)));         }         int_levels.sort();         levels.clear();                  for (int i = 0; i < int_levels.size; i++) {             levels.add(String.valueOf(int_levels.get(i)));         }         return levels;     }      // Парсим ноты          public Array<Note> XMLparseNotes(String strLevel) {         try {             Element root = new XmlReader().parse(Gdx.files.internal("xml/levels/" + strLevel + ".xml")).getChildByName("notes");             Array<Element> xml_notes = root.getChildrenByName("note");                          for (Element el : xml_notes) {                 Note note = new Note();                                  note.setNote(el.getText());                 note.setDelay(el.getAttribute("delay"));                                  this.notes.add(note);             }                      } catch (IOException e) {             e.printStackTrace();         }         return this.notes;     }      // Парсим позицию для звезд. Знаю знаю, можно было сделать это при парсинге уровня, но мне так легче потом читать этот код, если разбить его по задачам          public Map<String, Array<String>> getPos(String strLevel) {         try {             Element root = new XmlReader().parse(Gdx.files.internal("xml/levels/" + strLevel + ".xml")).getChildByName("positions");                          Array<Element> xml_pos = root.getChildrenByName("position");             for (Element el : xml_pos) {                 Array<String> xy = new Array<String>();                 xy.add(el.getAttribute("x"));                 xy.add(el.getAttribute("y"));                 this.starsPos.put(el.getAttribute("note"), xy);             }                      } catch (IOException e) {             e.printStackTrace();         }         return this.starsPos;     } } 

Осталось немного. Правда. Теперь, раз уж я заикнулся про многоязыковую поддержку, давайте создадим я немного поясню, как это будет. За основу берем локаль пользователя. Для нас она начинается с символом ru, для англичан с en и так далее. Я перевел приложение на два языка, поэтому языковой файл будет таким (и поэтому в коде метода XMLparseLangs немного странное условие):

Файл langs.xml

<?xml version="1.0"?> <langs>     <lang key="en">         <string key="Play">Play</string>         <string key="Exit">Exit</string>         <string key="Again">Again</string>         <string key="Levels">Levels</string>         <string key="YouWin">You win!</string>         <string key="Constellation">Constellation</string>                  <!-- Levels -->         <string key="level_1">Canes Venatici</string>         <string key="level_2">Triangulum</string>         <string key="level_3">Equuleus</string>         <string key="level_4">Apus</string>         <string key="level_5">Sagitta</string>         <string key="level_6">Musca</string>         <string key="level_7">Ursa Minor</string>         <string key="level_8">Orion</string>         <string key="level_9">Ursa Major</string>         <string key="level_10">Eridanus</string>         <string key="level_11">Lacerta</string>     </lang>     <lang key="ru">         <string key="Play">Играть</string>         <string key="Exit">Выход</string>         <string key="Again">Повторить</string>         <string key="Levels">Уровни</string>         <string key="YouWin">Вы победили!</string>         <string key="Constellation">Созвездие</string>                  <!-- Levels -->         <string key="level_1">Гончие псы</string>         <string key="level_2">Треугольник</string>         <string key="level_3">Малый Конь</string>         <string key="level_4">Райская Птица</string>         <string key="level_5">Стрела</string>         <string key="level_6">Муха</string>         <string key="level_7">Малая медведица</string>         <string key="level_8">Орион</string>         <string key="level_9">Большая медведица</string>         <string key="level_10">Эридан</string>         <string key="level_11">Ящерица</string>     </lang> </langs> 

Как видно, мы берем аттрибут и по нему определяем, что отдавать пользователю. Теперь нужно сделать еще кое-что. Создать XML файлы звезд, нот, уровней. Сделаем это.

Файл stars.xml

<?xml version="1.0"?> <stars>     <star files="c5" />     <star files="c#5" />     <star files="d5" />     <star files="d#5" />     <star files="e5" />     <star files="f5" />     <star files="f#5" />     <star files="g5" />     <star files="g#5" />     <star files="a5" />     <star files="a#5" />     <star files="b5" />          <star files="c6" />     <star files="c#6" />     <star files="d6" />     <star files="d#6" />     <star files="e6" />     <star files="f6" />     <star files="f#6" />     <star files="g6" />     <star files="g#6" />     <star files="a6" />     <star files="a#6" />     <star files="b6" /> </stars> 

Если бегло глянуть этот файл, то можно заметить, что немного слукавил, когда сказал, что для каждой ноты будет своя звезда. Я сделал разное представление звезд в разной тональности. Зачем? Для улучшения звучания, так как если взять более-менее интересное созвездие, то можно заметить, то оно состоит, как минимум из 8-9 звезд, а писать мелодию для 8-9 разных нот не очень-то хотелось, вот я и решил немного упростить себе жизнь, добавив еще одну октаву.
Теперь приведу файл(для примера) уровня.

Файл 1.xml

<?xml version="1.0"?>  <level>     <notes>         <note delay="0.02f">d5</note>         <note delay="0.05f">a6</note>         <note delay="0.05f">d6</note>         <note delay="0.05f">f#6</note>                  <note delay="0.02f">e5</note>         <note delay="0.05f">a6</note>         <note delay="0.05f">c#6</note>         <note delay="0.05f">e6</note>                  <note delay="0.02f">d6</note>         <note delay="0.05f">f#6</note>         <note delay="0.05f">a6</note>         <note delay="0.05f">d5</note>     </notes>     <positions>         <position note="d5" x="5" y="35" />         <position note="a6" x="20" y="43" />         <position note="d6" x="40" y="50" />         <position note="f#6" x="55" y="45" />         <position note="e5" x="67" y="37" />         <position note="c#6" x="77" y="47" />         <position note="e6" x="90" y="50" />     </positions> </level> 

Как видно, сначала мы определяем последовательность нот и их задержку, а затем определяем позицию каждой уникальной ноты в процентом отношении. Кажется это все. Если что-то забыл, жду комментариев. Также, жду критики и советов. Если кому-нибудь будет интересно в следующей статье я могу описать процесс подключения AdMob к нашей игре, рассказать как и откуда я брал звуки для игры, и также рассказать о том, как я выкладывал игру в Google Play. Спасибо за внимание!

Файлы проекта и пример готовой игры.

Нужно ли написать еще одну статью, о которой я говорил в конце поста?

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Никто ещё не голосовал. Воздержавшихся нет.

ссылка на оригинал статьи http://habrahabr.ru/post/224223/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *