html текст
All interests
  • All interests
  • Design
  • Food
  • Gadgets
  • Humor
  • News
  • Photo
  • Travel
  • Video
Click to see the next recommended page
Like it
Don't like
Add to Favorites

Как я писал Pacman’a и что из этого получилось. Часть 2


Здравствуй, хабр! Во второй части статьи я продолжу рассказ о том, как я писал клон игры Pacman. Первую часть можно почитать здесь.
С момента, когда я последний раз работал над пакманом прошло порядка трех недель. Прошла большая часть сессии, стало немного больше времени и я решил продолжить. В этот момент появилось желание доделать игру до состояния, когда ее можно будет выложить в Google Play Market, хотя в самом начале разработки я об этом даже не помышлял. Кроме того, доделывание до играбельного состояния – неплохая тренировка. Где-то я слышал, что игры (да и вообще приложения) стоит доделывать.
Напомню, что разработка игры велась с использованием Android NDK (С++) и OpenGL ES 2.0.



Для начала я составил список того, что, как я считал, необходимо для окончания работы над игрой:
  • Бонусы
  • Вывод текста
  • Музыка и звуки
  • Перманентное сохранение данных
  • Более красивая анимация и дизайн

Теперь подробнее, по пунктам:

Бонусы

Бонусы в игре нужны для разнообразия. Чтобы не тратить на них много времени, я ввел новый абстрактный класс Bonus, от которого тут же унаследовал LifeBonus. Как нетрудно догадаться, LifeBonus дает игроку одну жизнь. Надо сказать, бонусы весьма органично вписались в уже существующую иерархию:

На этом я пока остановился. Создать другие бонусы крайне легко, стоит лишь унаследовать их от Bonus’a.
В связи с бонусами стоит упомянуть класс Statistics. Этот класс нужен для сбора различной статистики, такой как вход/выход/пауза уровня, подсчет набранных очков и времени внутри уровня. Вся эта статистика собирается и может быть использована для создания таблицы достижений или даже сетевых таблиц рекордов. Внутри класс Statistics реализован в виде детерминированного конечного автомата.

Вывод текста

Сначала я хотел обойтись без текстовой информации вовсе, потому что (ИМХО) встраивание текста влечет за собой костыли. Оказалось, что обходиться без текста сложно, проще было реализовать его вывод.

Для вывода текста я воспользовался простым приемом: графическое представление символов моноширинного шрифта берется прямоугольниками из текстуры примерно такого вида, как на рисунке.
Первый символ – пробел, остальные идут подряд. Разлиновка на рисунке нужна лишь для удобства (видно базовую линию и то, что все символы выравнены). В приложении текстура такая же, но с прозрачным фоном. Правильнее было бы рендерить шрифт в текстуру на этапе выполнения, а не хранить статичную текстуру, но это только добавило бы сложности, т.к. непонятно, как выравнивать символы в прямоугольниках.
Для вывода текста разработан специальный элемент GUI — Label, наследник Control’a. Он используется в заголовке окна игры для вывода игровой статистики, в меню Win/GameOver для оповещения игрока о выигрыше или проигрыше соответственно.

Звук

Редкая игра обходится без звука (пожалуй, я с ходу не смогу назвать таких игр). Поэтому я решил добавить фоновую музыку и игровые звуки в свою игру тоже.

Техническая часть

До этого у меня не было опыта работы со звуком. Здесь есть как минимум 3 варианта:
  • Использовать jni и проигрывать звуки, используя API, предоставляемые Android SDK
  • Использовать OpenSL ES
  • Использовать OpenAL

Первый вариант я отбросил сразу, поскольку посчитал, что это не совсем изящное решение. Выбор из двух оставшихся был сделан в пользу OpenSL ES (об этом я написал статью, заработав тем самым инвайт сюда).
Для работы с музыкой разработан класс Audio, который имеет набор статических методов для включения той или иной фоновой музыки, быстрого проигрывания звуков и управления слышимостью музыки и звуков (по отдельности друг от друга).
Пользователь осуществляет управление из главного меню игры, в котором для этого есть подобия кнопок с состояниями – CheckBox, который унаследован от Control’a.

Композиторская часть

Сначала я хотел выбрать музыку и звуки из имеющихся в открытом доступе на огромном количестве музыкальных сайтов. Но эта затея провалилась, поскольку подобрать музыку оказалось проблематично для меня.
К счастью, ко мне на помощь пришел мой друг-музыкант Тимур Рамазанов, который согласился написать для меня треки. Лично мне музыка кажется очень подходящей к дизайну и настроению игры. Те, кому интересны другие его работы, могут ознакомиться с ними вконтакте или на soundcloud
Фоновая музыка разделена на две части: игровая и в меню. Она зациклена и сохранена в формате ogg. Игровые звуки сохранены в формате wav.

Сохранение информации

В процессе игры различная информация должна быть сохранена перманентно. Это, например, рекорды игрока или его настройки звука.
Для этого написана обертка над android.content.SharedPreferences. Обращение к обертке происходит через jni.
Код обертки
public class StoreManager {
	
	public static final String PACMAN_PREFERENCES = "com_zagayevskiy_pacman_store";
	
	private Context context;
	
    /*Сохраним ссылку на контекст*/
	public StoreManager(Context _context){
		context = _context;
	}
	
    /*Методы для сохранения и загрузки целых чисел и булевых величин. При желании можно расширить и другими типами*/
	public void saveBoolean(String key, boolean value){
		SharedPreferences sp = context.getSharedPreferences(PACMAN_PREFERENCES, Context.MODE_PRIVATE);
		SharedPreferences.Editor editor = sp.edit();
		editor.putBoolean(key, value);
		editor.commit();
	}
	
	public boolean loadBoolean(String key, boolean defValue){
		SharedPreferences sp = context.getSharedPreferences(PACMAN_PREFERENCES, Context.MODE_PRIVATE);
		return sp.getBoolean(key, defValue);
	}
	
	public void saveInt(String key, int value){
		SharedPreferences sp = context.getSharedPreferences(PACMAN_PREFERENCES, Context.MODE_PRIVATE);
		SharedPreferences.Editor editor = sp.edit();
		editor.putInt(key, value);
		editor.commit();
	}
	
	public int loadInt(String key, int defValue){
		SharedPreferences sp = context.getSharedPreferences(PACMAN_PREFERENCES, Context.MODE_PRIVATE);
		return sp.getInt(key, defValue);
	}
}


С++ код для обращения к StoreManager через jni
Store.h:
#include <stdlib.h>
#include <stdio.h>
#include <jni.h>

class Store {
public:
	static void init(JNIEnv* env, jobject _storeManager);
	static void saveBool(const char* name, bool value);
	static bool loadBool(const char* name, bool defValue);
	static void saveInt(const char* name, int value);
	static int loadInt(const char* name, int defValue);
private:
	static JavaVM* javaVM;
	static jobject storeManager;
	static jclass storeManagerClass;
	static jmethodID saveBoolId;
	static jmethodID loadBoolId;
	static jmethodID saveIntId;
	static jmethodID loadIntId;

	static JNIEnv* getJNIEnv(JavaVM* jvm);

};

Store.cpp:
/*env и _storeManager передаются при инициализации нативной библиотеки*/
void Store::init(JNIEnv* env, jobject _storeManager){
    /*Сохраним ссылку на Java-машину, понадобится позже*/
	if(env->GetJavaVM(&javaVM) != JNI_OK){
		LOGE("Can not Get JVM");
		return;
	}

	storeManager = env->NewGlobalRef(_storeManager);
	if(!storeManager){
		LOGE("Can not create NewGlobalRef on storeManager");
		return;
	}
	storeManagerClass = env->GetObjectClass(storeManager);
	if(!storeManagerClass){
		LOGE("Can not get StoreManager class");
		return;
	}

	saveBoolId = env->GetMethodID(storeManagerClass, "saveBoolean", "(Ljava/lang/String;Z)V");
	if(!saveBoolId){
		LOGE("Can not find method saveBoolean");
		return;
	}
    /*Аналогично для остальных методов*/
	}
}

void Store::saveBool(const char* name, bool value){
	LOGI("Store::saveBool(%s, %d)", name, value);
	JNIEnv* env = getJNIEnv(javaVM);

	if(!env){
		LOGE("Can not getJNIEnv");
		return;
	}

	jstring key = env->NewStringUTF(name);
	if(!key){
		LOGE("Can not create NewStringUTF");
	}

	env->CallVoidMethod(storeManager, saveBoolId, key, value);
}

bool Store::loadBool(const char* name, bool defValue){
	LOGI("Store::loadBool(%s, %d)", name, defValue);
	JNIEnv* env = getJNIEnv(javaVM);

	if(!env){
		LOGE("Can not getJNIEnv");
		return defValue;
	}

	jstring key = env->NewStringUTF(name);
	if(!key){
		LOGE("Can not create NewStringUTF");
	}

	return env->CallBooleanMethod(storeManager, loadBoolId, key, defValue);
}

/*Аналогично реализуются оставшиеся два метода load/saveInt()*/

/*Получаем указатель на JNIEnv для текущего потока, используя ссылку на Java-машину*/
JNIEnv* Store::getJNIEnv(JavaVM* jvm){
	JavaVMAttachArgs args;
	args.version = JNI_VERSION_1_6;
	args.name = "PacmanNativeThread";
	args.group = NULL;
	JNIEnv* result;
	if(jvm->AttachCurrentThread(&result, &args) != JNI_OK){
		result = NULL;
	}
	return result;
}



Более красивая анимация и дизайн

Первоначально анимировался у меня только Pacman. Хотелось сделать анимацию более красивой (а не в 4 кадра), и сделать анимацию для бонусов и врагов. Все это в одном стиле.
В какой-то момент возникла идея сделать Pacman’a в виде огненного шара, а его врагов – в виде капель воды.
Самый идеальный вариант для меня был – сделать красивую покадровую анимацию. Проблем в программном плане это не представляет, но зато есть проблема рисования кадров. Я столкнулся с проблемой поиска дизайнера и объяснения, что именно я хочу. Эту проблему я не решил. Потом некоторое время подумал и решил сделать полностью программную анимацию. А у дизайнера заказал только тайлы разных размеров, что обошлось мне в $50.

Программная анимация


Для того, чтобы сделать анимацию удобной в использовании, я реализовал два класса-наследника уже упоминавшегося выше IRenderable: Plume для анимации «шлейфа» и Pulsation для «пульcаций».
На скриншоте шлейфы различной длины имеют персонажи – Pacman и монстры, а пульсация – это точка большего размера в центре сердца. Так показана на карте дополнительная жизнь.
Идея обоих классов основана на эффекте «кисти». На каждом шаге объект класса Plume получает координаты анимируемого объекта и запоминает (или не запоминает, в зависимости от желаемой длины шлейфа – чем чаще запоминания, тем короче шлейф) их в контейнер-очередь. Затем, используя уже запомненные координаты, рисуются круги с помощью текстуры, аналогичной представленной ниже.


Зелено-черный градиент соответствует градиенту альфа-канала. Зеленый — полная непрозрачность, черный – полная прозрачность. Эта текстура генерируется при инициализации игры при помощи фрагментного шейдера и рендера в текстуру.
Чем старше координаты, тем меньший радиус рисуемого круга. Круги рисуются с наложением текстуры, указанной при создании объекта-шлейфа. Текстурные координаты при этом смещаются в зависимости от рисуемых координат и, дополнительно, по формуле спирали Архимеда (для того, чтобы при остановке персонажей анимация не застывала).
Для анимации Pacman’a и монстров используются шлейфы разной длины, с разными текстурами. Дополнительное требование к текстурам воды и пламени — они должны быть «зациклены», т.е. не должно быть видно стыков. Сам Pacman так же использует покадровую анимацию движения челюстей.
Аналогичным образом реализована пульсация, в которой градиентные круги различных размеров просто сменяют друг друга с определенной частотой.

Название и иконка приложения

При выборе названия хотелось обыграть то, что игра – клон Pacman’a, причем Pacman – огненный. При этом надо было не обидеть Namco. Были различные варианты: Fireman, Fire Man, Pyro Man, Pacman: Jaws of Fire. В итоге я остановился на Pyroman: Jaws of Fire. А отсылку к игре Pac-Man оставил в описании.
Иконку приложения нарисовал в фотошопе, обыграв огненность Pacman’a. Получилось похоже на золотую рыбку и, по-моему, забавно=)

Так же хотелось рассказать об участии в прошедшем конкурсе The Tactrick Android Developer Cup, в номинации «Games». Но, по сути, рассказать нечего, так как конкурс кончился внезапно — вывешиванием плашек «WINNERS» победителям и письмом «Спасибо за участие» остальным. Я не претендовал на какие-либо призовые места, но интересно было, на каком месте в зачете буду. Пусть 66 из 66, но будет понятно, что как-то программы оценивали.

Игра доступна на github.

Благодарности

Хочу сказать спасибо моей девушке Юле, за понимание и поддержку. В её честь нарисован первый уровень
Так же хочу поблагодарить моего гуру и наставника — Булата Танирбергена за дружескую поддержку и убеждение, что всё в моих силах
Рамазанову Тимуру за треки к игре — спасибо.
Отдельные благодарности компании ZeptoLab, благодаря которой я теперь достаточно хорошо знаю Android NDK, прочитал книгу Сильвена Ретабоуила о NDK и книгу Стефана Дьюхерста «Скользкие места С++», таким образом подняв свой программистский уровень.
Читать дальше
Twitter
Одноклассники
Мой Мир

материал с habrahabr.ru

1

      Add

      You can create thematic collections and keep, for instance, all recipes in one place so you will never lose them.

      No images found
      Previous Next 0 / 0
      500
      • Advertisement
      • Animals
      • Architecture
      • Art
      • Auto
      • Aviation
      • Books
      • Cartoons
      • Celebrities
      • Children
      • Culture
      • Design
      • Economics
      • Education
      • Entertainment
      • Fashion
      • Fitness
      • Food
      • Gadgets
      • Games
      • Health
      • History
      • Hobby
      • Humor
      • Interior
      • Moto
      • Movies
      • Music
      • Nature
      • News
      • Photo
      • Pictures
      • Politics
      • Psychology
      • Science
      • Society
      • Sport
      • Technology
      • Travel
      • Video
      • Weapons
      • Web
      • Work
        Submit
        Valid formats are JPG, PNG, GIF.
        Not more than 5 Мb, please.
        30
        surfingbird.ru/site/
        RSS format guidelines
        500
        • Advertisement
        • Animals
        • Architecture
        • Art
        • Auto
        • Aviation
        • Books
        • Cartoons
        • Celebrities
        • Children
        • Culture
        • Design
        • Economics
        • Education
        • Entertainment
        • Fashion
        • Fitness
        • Food
        • Gadgets
        • Games
        • Health
        • History
        • Hobby
        • Humor
        • Interior
        • Moto
        • Movies
        • Music
        • Nature
        • News
        • Photo
        • Pictures
        • Politics
        • Psychology
        • Science
        • Society
        • Sport
        • Technology
        • Travel
        • Video
        • Weapons
        • Web
        • Work

          Submit

          Thank you! Wait for moderation.

          Тебе это не нравится?

          You can block the domain, tag, user or channel, and we'll stop recommend it to you. You can always unblock them in your settings.

          • habrahabr.ru
          • домен habrahabr.ru

          Get a link

          Спасибо, твоя жалоба принята.

          Log on to Surfingbird

          Recover
          Sign up

          or

          Welcome to Surfingbird.com!

          You'll find thousands of interesting pages, photos, and videos inside.
          Join!

          • Personal
            recommendations

          • Stash
            interesting and useful stuff

          • Anywhere,
            anytime

          Do we already know you? Login or restore the password.

          Close

          Add to collection

             

            Facebook

            Ваш профиль на рассмотрении, обновите страницу через несколько секунд

            Facebook

            К сожалению, вы не попадаете под условия акции