Здравствуйте! Не прошло суток с момента публикации первой части статьи, а я не могу спать, так как есть незаконченное дело и нужно дописать статью. Приступим.
Оговорюсь еще раз. Я шибкий не знаток 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);
}
}
Ничего сложного. Это «актер», который устанавливается по размеру экрана пользователя и делает нашу игру более похожей на звездное небо. Примерно так это должно выглядеть:
Теперь добавим его в 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.habrahabr.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. Поскольку, там много кода, я решил его спрятать под спойлер.
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. Так, мы еще не научили нашу парсить звезды и ноты. Сделаем это.
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 немного странное условие):
<?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 файлы звезд, нот, уровней. Сделаем это.
<?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 разных нот не очень-то хотелось, вот я и решил немного упростить себе жизнь, добавив еще одну октаву.
Теперь приведу файл(для примера) уровня.
<?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. Спасибо за внимание!
Файлы проекта и пример готовой игры.
Автор: newca9h