Пишем движок игры под Android – tutorial часть 6 (заключительная)

Поскольку я тут выложил небольшой игровой движок для Android, было бы не честным просто закончить на этому серию уроков. Потому как движок — это хорошо а какая-нить игра на этом движке — еще лучше!

Именно этим — написанием игры счас и займемся!

По традиции будем писать крестики-нолики :-). Во-первых потому что уже есть небольшой опыт, а во-вторых потому что это просто пример использования движка.

Начну с класса который обеспечивает логику игры. Добавляем в наш проект (который можно скачать в самом конце вот этого урока)  новый пакет. Называем его ru.davidmd.tictactoe.

В этом пакете создаем новый класс ticTacToeGame вот с таким кодом:

package ru.davidmd.tictactoe;

public class TicTacToeGame {

	int[][] field = {{0,0,0},{0,0,0},{0,0,0}};

	boolean humanMove=true;
	public boolean GO;
	public int winner;

	public TicTacToeGame(){

	}

	/**
	 * Запуск новой игры
	 */
	public void starGame()
	{
		GO=false;
		winner=0;
		for(int i=0; i<3; i++)
		{
			for (int j=0; j<3; j++)
			{
				this.field[i][j]=0;
			}
		}
	}

	/**
	 * ход человека
	 * @param i
	 * @param j
	 * @return возвращает истину если человек может пойти на поле (i,j)
	 */
	public boolean humanMove(int i, int j)
	{
		boolean par = true;
		if (this.field[i][j]!=0) par = false;
		else
		{
			this.field[i][j]=1;
		}
		return par;
	}

	/**
	 * @return возвращает истину если еще остались свободные клетки на поле
	 * или ложь если таких клеток нет
	 */
	public boolean isMoves()
	{
		boolean par=false;
		for (int i =0 ; i< 3; i++)
		{
			for (int j = 0; j<3; j++)
			{
				if (field[i][j]==0)
				{
					par = true;
					break;
				}
				if (par)
				{
					break;
				}
			}
		}
		if (!par) GO=true;
		return par;
	}

	/**
	 * Случайный ход компьютерного игрока
	 */
	public void computerMove()
	{
		if (this.isMoves()){
		boolean par = true;
		int i;
		int j;
		while (par)
		{
			i = (int)(Math.random()*3);
			j = (int)(Math.random()*3);
			if (this.field[i][j]==0)
			{
				this.field[i][j]=2;
				par=false;
			}
		}
		}
	}

	/**
	 * Определеяет есть ли победитель или таковой не определен
	 * @return 0 - если победителя нет
	 * 		1 - если победил человек
	 * 		2 - если победил комп, хотя с таким AI как в {@link computerMove()} это маловероятно 🙂
	 */
	public int isGameOver()
	{

		    // Проверка по горизонтали
		    for (int i = 0; i < 3; i++) {
		        if (field[i][0] == field[i][1] && field[i][1] == field[i][2]
		                && field[i][0] != 0) {
		            winner = field[i][0];
		            break;
		        }
		    }

		    // Проверка по вертикали если победитель пока не найден
		    if (winner == 0) {
		        for (int i = 0; i < 3; i++) {
		            if (field[0][i] == field[1][i] && field[1][i] == field[2][i]
		                    && field[0][i] != 0) {
		                winner = field[0][i];
		                break;
		            }
		        }
		    }

		    // Проверка главной диагонали если победитель пока не найден
		    if (winner == 0) {
		        if (field[0][0] == field[1][1] && field[1][1] == field[2][2]
		                && field[0][0] != 0) {
		            winner = field[0][0];
		        }
		    }

		    // Проверка побочной диагонали если победитель пока не найден
		    if (winner == 0) {
		        if (field[2][0] == field[1][1] && field[1][1] == field[0][2]
		                && field[2][0] != 0) {
		            winner = field[2][0];
		        }
		    }
		    // Возвращаем победителя или ноль, если такового пока нет
		    if (winner!=0) GO=true;
		return winner;
	}

	/**
	 * @return возвращает игровую матрицу в которой 0 - пустое поле
	 * 	1 - поле крестиков (игрок)
	 * 	2 - поле ноликов  (AI)
	 */
	public int[][] getGameMatrix()
	{
		return field;
	}
}

Если внимательно присмотреться, то практически все методы которые здесь есть взяты отсюда 🙂 Единственное отличие заключается в том, что теперь игровое поле — это массив целых числе, а не символов как в том примере. Ну и может имена методов немного другие. Общая же суть не поменялась.

Однако логика игры имеет мало общего с использованием нашего движка. Поэтому не будем на ней заострять внимание. Лучше перейдем к самой игре.

Создадим новый класс mainActivity, унаследованный от обычной Activity. Вот код этого класса с очень подробными комментами

 

package ru.davidmd.tictactoe;

import android.net.Uri;

import ru.davidmd.myengine.mPoint;
import ru.davidmd.myengine.mScene;
import ru.davidmd.myengine.mSettings;
import ru.davidmd.myengine.mSimpleSprite;
import ru.davidmd.myengine.mSurfaceView;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

public class mainActivity extends Activity implements OnTouchListener {

	mSurfaceView sf;
	mScene mainScene;
	mSimpleSprite backGround;
	mSimpleSprite humanWins;
	mSimpleSprite humanLoses;
	mSimpleSprite nowinner;
	mSimpleSprite davidmd;
	mSimpleSprite o;
	mSimpleSprite x;
	int w, h;
	int step;
	TicTacToeGame game = new TicTacToeGame();

	float px, py;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

	}

	@Override
	public void onStart() {
		super.onStart();
		// Устанавливаем настройки в классе mSettings
		mSettings.GenerateSettings(this.getWindowManager().getDefaultDisplay());

		// И задаем желаемый фреймрейт
		mSettings.setTargetFrameRate(25);

		// Создаем и загружаем необходимые спрайты
		backGround = new mSimpleSprite("bg.png", this.getAssets());
		humanWins = new mSimpleSprite("hw.png", this.getAssets());
		humanLoses = new mSimpleSprite("hl.png", this.getAssets());
		nowinner = new mSimpleSprite("nw.png", this.getAssets());
		davidmd = new mSimpleSprite("dmd_ru.png", this.getAssets());

		// создаем сцену с двумя слоями
		// (ее ширина и высота настроятся
		// автоматически в классе mSurfaceView)
		mainScene = new mScene(0, 0, 2);
		// устанавливаем самый нижний слой сцены как текущий
		mainScene.setCurLay(0);
		// добавляем на этот слой спрайт backGround
		mainScene.addItem(backGround);
		// устанавливаем текущим верхний слой
		mainScene.setCurLay(1);

		// Создаем нвый mSurfaceView и добавляем
		//на него только что созданную сцену
		sf = new mSurfaceView(this, mainScene);

		// устанавливаем заголовок (от нефиг делать)
		this.setTitle("mEngine Example");

		// Устанавливаем обработчик касаний для
		// нашей активити ее саму
		sf.setOnTouchListener(this);

		// добавляем на активити созданый ранее
		// mSurfaceView
		this.setContentView(sf);

		// получаем ширину и высоту дисплея
		int w = this.getWindowManager().getDefaultDisplay().getWidth();
		int h = this.getWindowManager().getDefaultDisplay().getHeight();

		// и исходя из них получаем шиг для рисования
		// спрайтов крестика и нолика 🙂
		step = Math.min(w, h) / 3;

		// настраиваем спрайт
		davidmd.setX(w / 2 - davidmd.getWidth() / 2);
		davidmd.setY(step * 4);

		// втыкаем его на сцену
		mainScene.addItem(davidmd);
	}

	@Override
	public void onPause() {
		// Это то что происходит когда активити уходит на паузу
		// например когда нажали кнопку хоум
		super.onPause(); 

		// просто завершаем приложение.
		// нечего ему висеть в воздухе.
		System.runFinalizersOnExit(true);
		System.exit(0);
	}

	// метод который переводит экранные координаты в координаты
	// на игровом поле
	// возвращает точку mPoint (просто в ней удобнее
	// хранить два значения: по x и по y)
	private mPoint ScreenToCell(float x, float y) {
		float xx = x;
		float yy = y;

		int tmp;
		mPoint p;
		tmp = Math.min(w, h);
		if (mainScene.getOrient() == mScene.ORIENTATION_HOR) {
			xx = xx - tmp;
		} else {
			yy = yy - tmp;
		}
		xx = xx / step;
		yy = yy / step;
		if (xx > 2)
			xx=2;
		if (yy > 2)
			yy=2;
		p = new mPoint(xx, yy);
		return p;

	}

	// Обновляет все спрайты на сцене
	// исходя из игорвого поля
	public void updateScene(int[][] a) {
		// сначала очищаем верхний слой
		mainScene.getLayerByNum(1).clear();
		// а потом в цикле добавляем на него спрайты
		// если в клетке должен быть нолик ставим туда нолик
		// если должен быть крестик пихаем туда крестик
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 3; j++) {
				if (a[i][j] == 1) {
					x = new mSimpleSprite("x.png", this.getAssets());
					x.setX(i * step);
					x.setY(j * step);
					mainScene.addItem(x);
				}
				if (a[i][j] == 2) {
					o = new mSimpleSprite("o.png", this.getAssets());
					o.setX(i * step);
					o.setY(j * step);
					mainScene.addItem(o);
				}
			}
		}
		// ну и добавляем волшебный спрайт по тапу на котором
		// происходит переход на мой сайт 🙂
		mainScene.addItem(davidmd);
	}

	// Ну и собственно сам метод
	// обработки тапов на экран
	@Override
	public boolean onTouch(View arg0, MotionEvent evt) {
		// смотрим, если действие пользователя есть
		// MotionEvent.ACTION_DOWN (положил палец на экран)
		if (evt.getAction() == MotionEvent.ACTION_DOWN) {
			// то мы реализуем саму игру
			if (!game.GO) { // если игра еще не окончена
				// то запоминаем координату куда
				// пользователь опустил палец
				int x = (int) evt.getX();
				int y = (int) evt.getY();

				// переводим экранные координаты
				// в координаты на игровом поле
				mPoint tmp = this.ScreenToCell(x, y);

				// и пытаемся походить
				if (game.humanMove((int) tmp.getX(), (int) tmp.getY())) {

					// если попытка удалась,
					// т.е. если пользователь может поставить крестик
					// в выбраное поле, тогда обновляем сцену
					this.updateScene(game.getGameMatrix());

					// и если после хода пользователя игра закончиалсь
					// и победил человек
					if (game.isGameOver() == 1) {
						// то выводим спрайт который об этом говорит
						mainScene.addItem(humanWins);
					} else {
						// А если победил не человек
						if (!game.isMoves()) { // но ходов уже не осталось,
							mainScene.setCurLay(1);
							// тогда выодим спрайт о том, что
							// победителя не выявлено и вообще ничья
							// победила дружба!
							mainScene.addItem(nowinner);
						} else {
							// ну а в противном случае даем походить компу
							game.computerMove();
							//еще раз обновляем сцену
							this.updateScene(game.getGameMatrix());
							// и если игра закончиалась и комп выиграл
							if (game.isGameOver() == 2) {
								// то выводим уведомление об этом
								mainScene.setCurLay(1);
								mainScene.addItem(humanLoses);
							} else {
								if (!game.isMoves()) {
									// а если не выиграл то тоже
									// уведомляем о победе
									// дружбы 🙂
									mainScene.setCurLay(1);
									mainScene.addItem(nowinner);
								}
							}
						}
					}
				}
			} else {
				// вот это выполняется если игра уже закончена
				// запускается новая игра
				game.starGame();
				// и обновляется сцена
				this.updateScene(game.getGameMatrix());
			}
		}

		if (evt.getAction() == MotionEvent.ACTION_UP) {

			// а вот это происходит когда
			// пользователь нажал конпку перехода на мой сайт
			if (davidmd.equals((mSimpleSprite) mainScene.selectSprite(
					evt.getX(), evt.getY()))) {
				Intent inte = new Intent(android.content.Intent.ACTION_VIEW,
						Uri.parse("http://davidmd.ru/уроки-по-android/"));
				this.startActivity(inte);
				this.finish();
			}
		}
		return true;
	}

}

Теперь идем в AndroidManifest.xml и заставляем его выглядеть вот так:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="ru.davidmd.tictactoe"
      android:versionName="0.1" android:versionCode="1">
    <uses-sdk android:minSdkVersion="4" />
    <uses-permission android:name="android.permission.WRITE_OWNER_DATA"></uses-permission>
    <uses-permission android:name="android.permission.SET_DEBUG_APP"></uses-permission>

    <application android:enabled="true" android:label="@string/app_name" android:icon="@drawable/icon" android:debuggable="true">
        <activity android:name="ru.davidmd.tictactoe.mainActivity"
                  android:label="@string/app_name"
                  android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Ну а теперь, если добавить в папку assets все необходимые спрайты, то можно собрать игру и попробовать поиграть. Предупреждаю, бот полный идиот :-).

Ну а тут можно скачать все вкусности типа самого apk: TicTacToe и исходников: mEngine. Приятно повеселиться! Есть вопросы? Пишите в коментах!

UPD: продолжение здесь

 

Вам понравилось? Было полезно? Поделитесь!

Опубликовать в Facebook
Опубликовать в Google Buzz
Опубликовать в Google Plus
Опубликовать в LiveJournal
Опубликовать в Мой Мир
Опубликовать в Одноклассники
Опубликовать в Яндекс
Запись опубликована в рубрике Android, Программирование с метками , , , , , , , . Добавьте в закладки постоянную ссылку.

19 комментариев на «Пишем движок игры под Android – tutorial часть 6 (заключительная)»

  1. wil говорит:

    Спасибо большое за статьи!
    Вопрос. В эмуляторе экран прорисовывается заметными рывками. То есть при нажатии куда либо, значки ходом на заметное время пропадают, а затем опять отрисовываются. Это из-за тормознутости эмулятора или что?

    • davidmd говорит:

      Незачто! Надеюсь что-то будет вам полезно 🙂
      Эмулятор гораздо медленнее любого (даже самого простого) реального устройства выводит графику. На устройствах как правило стоят ускорители, а в эмуляторе его нет 🙁 Я сам когда только начал с графикой экспериментировать долго мучался с этими тормозами 🙂 пока не запустил прогу на своем телефоне 🙂

      • wil говорит:

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

  2. Юрий говорит:

    Огромное спасибо за статьи. Очень полезны! У меня есть вопрос, возможно глупый: как из исходников получить в дистрибутив *.apk?)

    • davidmd говорит:

      Спасибо за комментарий!
      Для того чтобы получить апк необходимо импортировать прект в эклипс и если у Вас установлена автоматическая сборка (а по умолчанию она установлена) в эклипсе посмотреть папку bin. Скорей всего там уже будет апк!

  3. Алексей говорит:

    Добрый день!

    Спасибо за отличные статьи, но у меня возник вопрос.
    Если сцена достаточно динамична — возможно, что 2 или более спрайта будут находиться друг над другом. И пусть они находятся на одном слое. В этой ситуации взять первый попавшийся — не всегда то, что нужно. Иногда нужно взять тот, что отрисовывается выше(так сказать, ближе к пользователю) других.
    Т. к. отрисовка происходит в прямом порядке массива(или списка) спрайтов, то самым верхним нарисуется самый последний спрайт слоя.
    Тогда, чтобы взять верхний спрайт, может быть, стоит брать первый попавшийся(по порядку) с конца массива в конкретной точке? Вроде бы, он и будет верхним в плане отрисовки.

    Если я что-то не так понял, то заранее приношу свои извинения за дурацкое предложение =)

    • davidmd говорит:

      Добрый!
      Вы все правильно поняли! Для того чтоб организовать такой выбор достаточно просто просматривать список спрайтов на слое с другой стороны! Ну исходники у Вас есть 🙂 Я счас дописываю статью о том, как я доработал движок и в ней выложу исходники последней версии.

  4. wil говорит:

    Как сделать frame анимацию mSimpleSprite не подскажете? Сделать mSimpleSprite extends ImageView implements WilPoint и использовать AnimationDrawable?

  5. Алексей говорит:

    Благодарю, интересные статьи, дают общие понятия о разработки игр. Но вот стоит ли замахиваться на создания собственного движка? Может стоит взять действующий и зарекомендовавший себя как вполне быстрый и надежный? Например, многие хорошо отзываются об Andengine. Пытаюсь его использовать, но так как имею мало опыта в программировании, особенно ООП, то сложновато. Возможно стоит написать статьи про написание игр на Andengine?

    • davidmd говорит:

      На самом деле движок не выдает никаких потрясающий результатов по скорости или по надежности, однако он разрабатывался в целях обучения прежде всего самого себя 🙂 Ну и решил за одно его и выложить. Почему именно движок, а не просто какая-то игра? Потому что люблю повторно использовать код 🙂 К тому же на немного доделаном этом движке удалось без особых усилий построить как минимум одну игру. В конце концов изучение andengine — это конечно здорово, но однозначно принесет меньше пользы человеку, который только начинает разбираться в Android. imho.

  6. Алексей говорит:

    Кстати, хочу добавить, что AdSense не показывается (у меня во всяком случае, причем на 2 устройствах)

    • davidmd говорит:

      Ага, знаю, говорят почему-то заблокировали… Ну да и фиг с ним… Все равно я на нем ничего особо не зарабатывал 🙂

  7. Андрей говорит:

    Спасибо за интересные уроки!
    Хотелось бы узнать — что даёт «android.permission.SET_DEBUG_APP» в AndroidManifest.xml?

    • davidmd говорит:

      У меня без такого разрешения не получалось вести отладку на Acer Liquid (правда на новом телефоне все работает замечательно и без этого разрешения)

  8. Валерчик говорит:

    Добрый день!

    Я новичок в программировании, так что извините если вопрос глупо звучит. Вы можете подробно написать как используется движок в реализации класса mainActivity, т.к. в классе TicTacToeGame он не используется.
    Заранее спасибо.

  9. Костя говорит:

    Не подскажете в чем дело? запускаю в эмуляторе проект mEgine в нем два пакета:
    ru.davidmd.myengine — сам движок (все делал по вашему мануалу) и ru.davidmd.tictactoe — в нем два класса один с mainActivity другой TicTacToeGame, Eclipse не ругаеться ни на что но при запуске в эмуляторе — через Run Android Application, Platform 8.8, API Level 8 Получаю в Console такое сообщение:
    06-29 14:51:01.174: E/AndroidRuntime(283): FATAL EXCEPTION: main
    в эмуляторе соответственно «Force Close». Что ДЕЛАТЬ подскажите — задолбался я с эмулятором — в Eclipse -ни одной ошибки в эмуляторе ничего не работает, подскажите пожалуйста а то как дурак — куча проектов сделал — но ниче толком в эмуляторе запустить не могу!!!!!!!!!!!

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

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