Здравствуйте! Я решил попробовать себя на поприще game-dev'а и заодно рассказать и показать как это было.
Игра представляет собой экран, на котором расположены созвездия. Каждая звезда этого созвездия имеет свой цвет (нота). Например, ноту «До» обычно представляют красным цветом, а «Ми» — желтым. Вот что получится в итоге:
Итак, каждый уровень — новое созвездие и новая мелодия. Звезды играют первые четыре ноты, а затем вы должны повторить их в той же последовательности. Затем, к первым четырем нотам добавляются еще четыре и так далее, пока уровень не будет пройден.
Писать будем, используя фреймворк libGDX. Мне он больше всех понравился, как новичку в этом деле. Да и информации по нему я нашел больше. Итак, приступим.
Что нам понадобится:
- Eclipse
- Gradle
- Android SDK
- libGDX последней версии
- Голова + руки + терпение
Я не буду создавать проект вручную. Мне проще воспользоваться gdx-setup. Итак, запускаем ее:
java -jar gdx-setup.jar
Далее вводим:
- Name: «Songs of the Space»
- Package: «ru.yoursite.songs_of_the_space»
- Game Class: «MyGame»
- Destination: path/to/your/workspace/songs_of_the_space
- Android SDK: path/to/your/sdk
- libGDX Version: Nightlies
- Sub Projects: Desktop, Android
- Extentions: «Freetype»
Нажимаем Generate и, после окончания процесса, идем в Eclipse.
В Eclipse выбираем Import -> Gradle -> Gradle Project. Затем Browse.. ищем наш проект и затем Build. После завершения выбираем все проекты и наживаем Finish. После завершения у вас в списке проектов появятся наши проекты. Сразу идем в основной проект (core) -> MyGame.java. Очищаем все, что создал gdx, а также наследуемся не от ApplicationAdapter, а от Game. В итоге, класс должен получить вид:
public class MyGame extends Game {
@Override
public void create() {
}
@Override
public void render() {
super.render();
}
}
Далее. Создадим новый пакет, назовем его screens. И в нем три класса:
- MainMenuScreen — будет отвечать за начальный экран приложения
- LevelScreen — будет отвечать за экран выбора уровней
- PlayScreen — будет отвечать за игровой экран
Все они должны наследоваться от интерфейса Screen. Добавляем все методы, которые требует наше наследование, закрываем все, кроме MainMenuScreen. В нем пишем следующее:
public class MainMenuScreen implements Screen {
// наш основной класс
final MyGame game;
// Объявим все необходимые объекты
private Stage stage;
private TextButton play, exit;
private Table table;
private LabelStyle labelStyle;
// Конструктор принимает объект нашего основого класса (объяснения позже)
public MainMenuScreen(final MyGame gam) {
game = gam;
// Сцена -- она поможет существенно уменьшить количество кода и упростить нам жизнь
stage = new Stage(new ScreenViewport());
// Скин для кнопок. Изображения вы найдете по ссылке внизу статьи
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");
labelStyle = new LabelStyle();
labelStyle.font = game.font;
table = new Table();
table.setFillParent(true);
// Кнопка играть. Добавляем новый listener, чтобы слушать события касания. После касания, выбрирует и переключает на экран выбора уровней, а этот экран уничтожается
play = new TextButton("Играть", textButtonStyle);
play.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();
};
});
// Кнопка выхода. Вообще это не обязательно. Просто для красоты, ибо обычно пользователь жмет на кнопку телефона.
exit = new TextButton("Выход", textButtonStyle);
exit.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) {
Gdx.app.exit();
dispose();
};
});
table.add(play);
table.row();
table.add(exit);
stage.addActor(table);
Gdx.input.setInputProcessor(stage); // Устанавливаем нашу сцену основным процессором для ввода (нажатия, касания, клавиатура etc.)
Gdx.input.setCatchBackKey(true); // Это нужно для того, чтобы пользователь возвращался назад, в случае нажатия на кнопку Назад на своем устройстве
}
@Override
public void render(float delta) {
// Очищаем экран и устанавливаем цвет фона черным
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// Рисуем сцену
stage.act(delta);
stage.draw();
}
@Override
public void resize(int width, int height) {}
@Override
public void show() {}
@Override
public void hide() {}
@Override
public void pause() {}
@Override
public void resume() {}
@Override
public void dispose() {
// Уничтожаем сцену и объект game.
stage.dispose();
game.dispose();
}
}
Далее. Делаем имрорт зависимостей (Shift + Ctrl + O). И идем в основной класс MyGame.java. В него добавим следующее:
public class MyGame extends Game {
// Объявляем наш шрифт и символы для него (чтобы нормально читались русские буковки)
public BitmapFont font, levels;
private static final String FONT_CHARACTERS = "абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789][_!$%#@|\/?-+=()*&.;,{}"´`'<>";
@Override
public void create() {
// Я взял шрифт RussoOne с Google Fonts. Сконвертировал его в TTF. (как я понял, только ttf и поддерживается)
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(Gdx.files.internal("fonts/russoone.ttf"));
FreeTypeFontParameter param = new FreeTypeFontParameter();
param.size = Gdx.graphics.getHeight() / 18; // Размер шрифта. Я сделал его исходя из размеров экрана. Правда коряво, но вы сами можете поиграться, как вам угодно.
param.characters = FONT_CHARACTERS; // Наши символы
font = generator.generateFont(param); // Генерируем шрифт
param.size = Gdx.graphics.getHeight() / 20;
levels = generator.generateFont(param);
font.setColor(Color.WHITE); // Цвет белый
levels.setColor(Color.WHITE);
generator.dispose(); // Уничтожаем наш генератор за ненадобностью.
@Override
public void render() {
super.render();
}
}
Делаем импорты и создаем новый package с именем managers (например). В нем класс XMLparse.java. Зачем это нужно? Затем, что уровни и многое другое мы будем брать из xml файлов. Кстати, создайте в папке assets (android проект -> assets) папку xml. В ней папки:
- images
- fonts
- sounds
- xml
В папку fonts положите шрифт. А в папке xml, создайте папку levels. Обновите Desktop проект ( F5 ), чтобы он подхватил все это. И теперь, давайте наполним наш класс XMLparse.java. В него пишем следующее:
public class XMLparse {
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 {
dirHandle = Gdx.files.internal(System.getProperty("user.dir") + "/assets/xml/levels"); // хак для desktop проекта, так как он почему-то не видел этих файлов. Создайте символическую ссылку папки assets в в корне desktop-проекта на папку assets android-проекта
}
for (FileHandle entry : dirHandle.list()) {
levels.add(entry.name().split(".xml")[0]);
}
// Эту жесть я сделал потому что сортировка строк немного не верно сортирует уровни. В комментариях подскажут как это сделать красивее. Я не особо Java программист. Я только учусь :)
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;
}
}
Ну что? Давайте наконец наполним класс LevelScreen.java. Но перед этим создайте пару xml файлов в папке assets -> xml -> levels с именами 1.xml, 2.xml и так далее. А в класс напишем следующее:
public class LevelScreen implements Screen {
final MyGame game;
private Stage stage;
private Table table;
private LabelStyle labelStyle;
private TextButton level;
private Array<String> levels;
public LevelScreen(MyGame gam) {
game = gam;
stage = new Stage(new ScreenViewport());
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.levels;
textButtonStyle.up = skin.getDrawable("level-up");
textButtonStyle.down = skin.getDrawable("level-down");
textButtonStyle.checked = skin.getDrawable("level-up");
//Парсим наши уровни
XMLparse parseLevels = new XMLparse();
levels = parseLevels.XMLparseLevels();
labelStyle = new LabelStyle();
labelStyle.font = game.levels; // Берем размер шрифта из класса MyGame
table = new Table();
table.row().pad(20); // Новая строка + отступы
table.center();
table.setFillParent(true);
for (int i = 0; i < levels.size; i++) {
final String cur_level = levels.get(i);
level = new TextButton(cur_level, textButtonStyle);
level.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, cur_level)); // Передаем выбранный уровень в PlayScreen
dispose();
};
});
table.add(level);
// А эта жесть для того, чтобы переходить на новую строку при достижении количества в пять уровней в одной строке
float indexLevel = Float.parseFloat(String.valueOf(i)) + 1;
if (indexLevel % 5.0f == 0) table.row().padLeft(20).padRight(20).padBottom(20);
}
stage.addActor(table); // Добавляем нашу таблицу с уровнями на сцену
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 MainMenuScreen(game));
}
}
});
}
@Override
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act(delta);
stage.draw();
}
@Override
public void resize(int width, int height) {}
@Override
public void show() {}
@Override
public void hide() {}
@Override
public void pause() {}
@Override
public void resume() {}
@Override
public void dispose() {
stage.dispose();
game.dispose();
}
}
Ух, много уже написал. В следующей части (если эта пройдет все этапы публикации) мы сделаем следующее:
- Наполним класс PlayScreen
- Добавим объекты звезд, нот и еще кое-чего
Спасибо за внимание!
Автор: newca9h