Портирование игры из реального мира на Android

    Началось всё как в самом настоящем детективе: новогодние праздники, 31 декабря, родительский дом за много километров от москвы и что самое страшное — полное отсутствие интернета и телеканала 2x2. Мозг может работать в двух режимах — либо потреблять контент, либо создавать его. Так получилось, что у меня мозг в тот момент заработал во втором режиме. По случайному стечению обстоятельств, мне на глаза попалась давно забытая игра-головоломка «Пифагор»:



    И я решил «оцифровать» её.



    Головоломка Пифагор состоит из 7-ми геометрических фигурок, которые необходимо сложить таким образом, чтобы получилась фигура, указанная в задании. При решении задания необходимо использовать все 7 фигурок головоломки.

    Выбор платформы.

    Ну и в довесок ко всему, я давно хотел написать что-нибудь под «мобильник».

    Так как на ноуте стоял Eclipse с необходимыми приблудами для Android, и было несколько книжек про него, скачанных «про запас», то выбор был предопределён.

    Итак, решено: будем писать игрушку «Пифагор» для Android. Так же отмечу, что я не являюсь программистом, в моём понимании этого слова.

    Реализация идеи

    Не буду описывать как устанавливать и настраивать Eclipse и как создавать проект, так как про это и так много всего написано, наконец-то перейду к сущности повествования.

    Создаём проект с именем Pifagor.
    После создания у нас будет файл с именем Pifagor.java и содержимым:

    package Pifagor.android.game;
    
    import android.app.Activity;
    import android.os.Bundle;
    
    public class Pifagor extends Activity {
        /** Called when the activity is first created. */
    	
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            pifagorView = (PifagorView) findViewById(R.id.game);
        }   
    }
    


    Начало положено.
    Теперь создадим файл PifagorDraw.java в котором и будет реализована наша игра. Определим класс PifagorDraw который дочерним для класса Thread. Для нашего класса определим методы обработки событий он экрана, сброс игры в начальное состояние и переопределим метод run:

    package Pifagor.android.game;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Paint.Style;
    import android.view.MotionEvent;
    import android.view.SurfaceHolder;
    
    public class PifagorDraw extends Thread {
    	private SurfaceHolder appScreen;
    	private boolean appRunning;			// флаг работы потока
    	private boolean appPaused;			// флаг паузы потока
    	private Paint appPaint;				// 
    	private Bitmap clearScreen;			// битмап для очистки экрана
    	private PifagorPuzzle Puzzles[];	// массив фигур
    	private int activePuzzle;			// номер активной фигуры
    	private long thisTime;				// переменная для сохранения времени нажатия на экран
    	private long downTime;				// переменная для сохранения времени отжатия от экрана
    	private PifagorTask Tasks[];		// массив заданий
    	private PifagorTask ResetPiktogram;	// пиктограмма для кнопки сброса
    	private int activeTask;				// номер активного задания
    	private int maxTasks;				// общее количество заданий
    	private boolean prevButton, nextButton, resetButton;	// флаги нажатия кнопок
    
    // Описание фигур
    	// большой квадрат
    	private int PuzzleTemplate1[][][] = {......};
    	// большой треугольник
    	private int PuzzleTemplate2[][][] = {......};
    	// малый квадрат
    	private int PuzzleTemplate3[][][] = {......};
    	// малый треугольник
    	private int PuzzleTemplate4[][][] = {......};
    	// косой паралепипед
    	private int PuzzleTemplate5[][][] = {......};
    
    // Пиктограмма кнопки "Сброс"
    	private int Pikto1[][] = {......};
    	
    // Описание заданий
    	private int Task1[][] = {......};
    	private int Task2[][] = {......};
    	private int Task3[][] = {......};
    	private int Task4[][] = {......};
    
    // Конструктор
    	public PifagorDraw(SurfaceHolder surfaceHolder, Context context) {
    		// определение начальных значений переменных 
    		appScreen = surfaceHolder;
    		appRunning = false;
    		appPaused = false;
    		activePuzzle = 0;
    		thisTime = 0;
    		downTime = 0;
    		prevButton = false;
    		nextButton = false;
    		resetButton = false;
    
                    // Стиль рисования
    		appPaint = new Paint();
    		appPaint.setColor(Color.BLUE);
    		appPaint.setStrokeWidth(2);
    		appPaint.setStyle(Style.STROKE);
    		
    		// создание экрана для очистки изображения
    		clearScreen = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.RGB_565);
    		
    		// определение и заполнение массива фигурок
    		Puzzles = new PifagorPuzzle[7];
    		Puzzles[0] = new PifagorPuzzle(......);
    		Puzzles[1] = new PifagorPuzzle(......);
    		Puzzles[2] = new PifagorPuzzle(......);
    		Puzzles[3] = new PifagorPuzzle(......);
    		Puzzles[4] = new PifagorPuzzle(......);
    		Puzzles[5] = new PifagorPuzzle(......);
    		Puzzles[6] = new PifagorPuzzle(......);
    		PuzzlesReset();
    		
    		// определение и заполнение массива заданий
    		maxTasks = 4;
    		activeTask = 0;
    		Tasks = new PifagorTask[maxTasks];
    		Tasks[0] = new PifagorTask(......);
    		Tasks[1] = new PifagorTask(......);
    		Tasks[2] = new PifagorTask(......);
    		Tasks[3] = new PifagorTask(......);
    		
    		// создание пиктограммы сброса
    		ResetPiktogram = new PifagorTask(......);
    	}
    	
    // установка флага работы процесса	
    	public void setRunning(boolean status) {
    		appRunning = status;
    	}
    	
    // обработка сбытий от экрана
    	public boolean Touch(MotionEvent event) {
    		int mouseX = (int)event.getX();
    		int mouseY = (int)event.getY();
    		switch(event.getAction()) {
    			case MotionEvent.ACTION_MOVE:					// обработка движения
    			        ......
    				break;
    			case MotionEvent.ACTION_DOWN:				       // обработка нажания
    				......
    				break;
    			case MotionEvent.ACTION_UP:		                	// обработка отпускания
    				......
    				break;
    		}
    		return true;  // возвращаем true, потому что события от экрана отработаны
    	}
    	
    // Функция сброса в игры в начальное состояние 	
    	public void PuzzlesReset() {
    		......
    	}
    	
    	@Override
    	public void run() {
    		while(appRunning) {
    			if (!appPaused) {
    				Canvas canvas = null;
    				try {
    					canvas = appScreen.lockCanvas();
    					synchronized(appScreen) {
    						
                                                    // здесь прописываем логику игры и отрисовку игровых объектов
                                                    // Например:
    
                                                    // очистка изображения
    						canvas.drawBitmap(clearScreen, 0, 0, null);
    					
    						// рисование фигур задач
    						for(int i=0;i<7;i++) {
    							Puzzles[i].Update();
    							Puzzles[i].Draw(canvas);
    						}	
    						
                                                    ......
                                                    
    						sleep(20);
    					}
    				} catch (Exception e) { }
    				finally {
    					if (canvas != null) { appScreen.unlockCanvasAndPost(canvas); }
    				}
    			}
    		}
    	}
    }
    


    Я прочитал про различные способы работы с графикой на Android, но более-менее пока разобрался с одним, его и буду использовать.

    Теперь создадим в проекте файл с именем PifagorView.java. В этом файле объявляем класс PifagorView, который является дочерним для класса SurfaceView и добавляем наследование интерфейса SurfaceHolder.Callback — этот класс будет связующим звеном.
    В созданном классе нам необходимо переопределить три унаследованных от SurfaceView метода для реагирования на изменения состояния View, а так же один метод для обработки событий от экрана(нажатие, перетаскивание, отпускание):

    package Pifagor.android.game;
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView;
    
    public class PifagorView extends SurfaceView implements SurfaceHolder.Callback {
    
    	private SurfaceHolder appScreen; 
    	private PifagorDraw appThread;
    	
    	public boolean surfaceCreated;
    	
    // конструктор
    	public PifagorView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		surfaceCreated = false;
    		appScreen = getHolder();
    		appScreen.addCallback(this);
    		
    		appThread = new PifagorDraw(appScreen, context);
    		setFocusable(true);		
    	}
    
    
    	@Override
    	public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
    		// TODO Auto-generated method stub		
    	}
    
    	@Override
    	public void surfaceCreated(SurfaceHolder holder) {
    		appThread = new PifagorDraw(holder, getContext(), screenWidth, screenHeight);
    		appThread.setRunning(true);
    		appThread.start();
    	}
    
    	@Override
    	public void surfaceDestroyed(SurfaceHolder arg0) {
    		boolean retry = true;
    		appThread.setRunning(false);
    		while(retry) {
    			try {
    				appThread.join();
    				retry = false;
    			} catch (InterruptedException e){ }
    		}	
    	}
    
            @Override
    	public boolean onTouchEvent(MotionEvent event) {
    		return appThread.Touch(event); // так как вся логика игры у нас прописана в классе PifagorDraw, то мы просто перенаправляет туда полученные события
    	}
    }
    


    Теперь нам надо подправить файл main.xml — мы создали наш объект View и теперь его надо расположить на экране:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        >
    	<Pifagor.android.game.PifagorView
    		android:id="@+id/game"
    		android:orientation="vertical"
    		android:layout_width="fill_parent"
    		android:layout_height="fill_parent"
    	/>
    </LinearLayout>
    


    Так же в проекте есть ещё два класса PifagorPuzzle, в который описывает геометрические фигурки головоломки, и класс PifagorTask, описывающий задания. Их рассматривать не будем.

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

    Во-первых, в классе PifagorView создадим методы CreateThread и TerminateThread (из названия понятно для чего они будут использоваться), и несколько изменим переопределённые методы SurfaceCreated и SurfaceDestoryed:
    public class PifagorView extends SurfaceView implements SurfaceHolder.Callback {
    
    	private SurfaceHolder appScreen; 
    	private PifagorDraw appThread;
    	
    	public boolean surfaceCreated;
    	
    	private int screenWidth;
    	private int screenHeight;
    	
    // конструктор
    	public PifagorView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		surfaceCreated = false;
    		appScreen = getHolder();
    		appScreen.addCallback(this);
    		
    		appThread = new PifagorDraw(appScreen, context, screenWidth, screenHeight);
    		setFocusable(true);		
    	}
    
    	
    // уничтожение процесса
    	public void terminateThread() {
    		boolean retry = true;
    		appThread.setRunning(false);
    		while(retry) {
    			try {
    				appThread.join();
    				retry = false;
    			} catch (InterruptedException e){ }
    		}	
    	}
    	
    // создание процесса
    	public void createThread(SurfaceHolder holder) {
    		appThread = new PifagorDraw(holder, getContext());
    		appThread.setRunning(true);
    		appThread.start();
    	}
    
    	@Override
    	public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
    		// TODO Auto-generated method stub		
    	}
    
    	@Override
    	public void surfaceCreated(SurfaceHolder holder) {
    		if (surfaceCreated == false) {
    			createThread(holder);
    			surfaceCreated = true;
    		}
    	}
    
    	@Override
    	public void surfaceDestroyed(SurfaceHolder arg0) {
    		terminateThread();
    	}
    	
    	
    	@Override
    	public boolean onTouchEvent(MotionEvent event) {
    		return appThread.Touch(event);
    	}
    }
    


    Во-вторых в классе Pifagor переопрделим методы onPause и onResume:
    public class Pifagor extends Activity {
        /** Called when the activity is first created. */
    	
    	private PifagorView pifagorView;
    	
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            pifagorView = (PifagorView) findViewById(R.id.game);
        }
        
        @Override
        public void onPause() {
        	super.onPause();
        	pifagorView.terminateThread();
        }
        
        @Override
        public void onResume() {
        	super.onResume();
        	if (pifagorView.surfaceCreated == true)
            {
                pifagorView.createThread(pifagorView.getHolder());
            }
        }
    }
    


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

    Полные исходники приводить не стал, если кому интересно, пишите в личку, вышлю.

    Ну что ж, запускаем и смотрим что получилось:

    Дизайнера для игрушки так и не нашёл. Если кто-то готов поучаствовать — пишите в личку.

    Что дальше?
    Игра написана, надо что-то делать дальше. Или убирать в архив, где она сгинет или попробовать выставить в Android Maket. Второе намного интереснее, по этому регистрируемся на market.android.com/publish, регистрируем Google Checkout (лучше регистрировать через IE, с остальными браузерами у меня были проблемы), платим 25$ и как обычно в самый последний момент пытаемся понять куда мы опять вляпались, то есть ищем информацию — как продвигать приложения в Android Market. Читаем статью и после этого составляем следующий план:
    • Публикуем бесплатную демо-версию игры, содержащую всего 5 заданий
    • Публикуем платную версию игры, с 30 заданиями
    • Публикуем обновление игры с некоторыми доработками
    • Пишем обзоры игры на различных интернет-ресурсах
    • Публикуем обновлённую версию игры с дополнительными 30 заданиями

      А так же параллельно составляем список функций, которые хотелось бы добавить в игру.

      Так как данная статья является реализацией третьего пункта, то поделюсь результатами первых двух:
      Демо-версию игры скачали чуть более 200 человек, активных установок около 100, что для первого приложения, на мой взгляд очень неплохо. Так же получил несколько советов по игре.

      Через неделю, после публикации демо-версии, опубликовал первую редакцию платной версии игры. Демо-версия была удалена — из-за того что потерял сертификат, которым подписывал приложение, и не смог загрузить обновление демо-версии — так что будьте аккуратнее. За первые 24 часа нахождения игры в маркете её купили 2 человека, причём оба из штатов, судя по профилю в google checkout. В течении недели была ещё одна продажа.

      Следуя плану, доработал и выложил обновлённую версию игры с дополнительными фичами.

      Как будут развиваться события дальше, отпишусь позднее.

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

      P.S. В планах портировать игру под Windows Phone 7 и если не пропадёт запал то и под iOS.
    Поделиться публикацией

    Похожие публикации

    Комментарии 36

      +6
      На англ. будет Pythagoras, а не Pifagor.
        0
        Да, спасибо, как-то не подумал об этом. В следующей версии исправлю.
          +6
          Не исправляйте.

          По Pythagoras будет гуглиться историческая личность, а по Pifagor — ваша программа.
        +1
          0
          Опередили. Но всё равно буду делать свою версию под iOS, лучше учиться на реальных задачах.
            0
            Да и дизайн там колхозный. Сделать круче — и будет лучше продаваться.
          +4
          >>В планах портировать игру под Windows Phone 7
          ненене, вам стоит отдохнуть, пока я… не важно :)
            0
            Предлагаю объединиться :-)
            +1
            Неплохая реализация старой доброй головоломки.
            Всё новое — хорошо забытое старое.
              0
              Задумка может и неплоха, но вот найдется ли много хоть сколько то желающих на
              >> платную версию игры, с 30 заданиями
              +4
              Спасибо, отличная головоломка! По-моему она еще называется танграм?
                0
                Танграм не совсем идентичная игрушка, но очень похожая. И там уж точно нет никаких авторских прав — известна в Европе под 200 лет, и непонятно сколько просуществовала раньше.
                –3
                Всегда было интересно узнать про юридическую сторону вопроса портирования.
                Ведь на эту игру ( геймплей? ) наверняка есть у кого-то права.
                Как быть в такой ситуаци?
                Есть ли реальные примеры когда првообладатель начинал качать права на такие портирования?
                Будут ли проблемы если просто сфоткать и обработать элементы игры, а не рисовать их самим?
                В общем интересует тема.
                  0
                  Учитывая упаковку это игры на картинке сомневаюсь что кто-то будет качать права. Игра очень древняя…
                    0
                    И насчет второго: сомневаюсь, что если это сфоткать будет красиво и удобно:(
                      0
                      Надо не сомневаться, а читать внимательно каменты…
                      1. Я спрашивал вообще про портирования… ведь согласитесь топик не единственный пример портирования.
                      2. Я говорил не про «тупо сфоткать и вставить в игру»… я писал про «обработать».
                      0
                      Я тоже задался таким вопросом, но знакомые юристы ничего не смогли точно сказать. Игра была приобретена в конце 80-ых, так что надеюсь все сроки вышли :-) хотя продолжаю изучать вопрос. Если будет какая-то информация, то я поделюсь.
                        0
                        Этой игре примерно столько лет, сколько Пифагору, хотя к Пифагору-то она отношения и не имеет.
                        0
                          0
                          А части можно поворачивать? Или только двигать?
                            0
                            Можно поворачивать. Сейчас делаю сплеш-скрин с подсказкой.
                              0
                              А флипить? Это обязательно :)
                            0
                            Есть подобная игра под андроид: market.android.com/details?id=com.bendroid.tangramprofree
                            В своё время играл, но она простая слишком, так что надоела под конец.
                              0
                              есть замечательный тег <pre> с которым Ваш код станет красивей и читабельней =)
                                0
                                или даже <code>
                                  0
                                  Или даже <source>
                                    0
                                    Да, Ваш получше будет =) Ато я так и не понял как пользоваться <code> =)
                                      0
                                      Спасибо, поправил.
                                      а то использовал получилось некрасиво.
                                      0
                                      Как им пользоваться?:) Я не догнал
                                    0
                                    Не хотите подумать над дизайном проекта? Готов поучаствовать.
                                      +4
                                      По-английски эта игра называется tangram, и её полный маркет: market.android.com/search?q=tangram&c=apps
                                        +1
                                        Вы украли мой комментарий :)
                                        В дополнение хотел дать статью на Вики Танграм
                                        0
                                        хорошая идея, удачи)
                                          0
                                          Всё чаще на Хабре появляются примеры успешного написания приложения с последующем внедрением на страницы Маркета. Надо подтягиваться и написать что-нибудь интересное, чтобы было что показать и о чем рассказать.

                                          Спасибо за статью. Удачи!
                                            0
                                            Как приятно радует глаз первая картинка — ностальгия по детству.

                                            Спасибо за статью. Удачи!
                                              0
                                              Спасибо :-)

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

                                            Самое читаемое