Pull to refresh

Сказ о Cocos2d-android

Reading time 6 min
Views 23K
Так уж получилось, что мне пришлось портировать игру с ios. Фреймворк выбирать не пришлось, им стал Cocos2d. До момента использования фреймворка мне довелось почитать отзывы о нем и они настораживали. Но как всегда надеялся на лучшее. И так, о своем опыте портирования и применения данного фреймворка пойдет этот рассказ. Данный пост будет полезен тем, кто присматривается к Cocos2s-android.


Мысль о том, как должно быть



По моему скромному мнению, архитектуру любой игры нужно проектировать по наименее зависимой от платформы схеме. К примеру, используя Cocos2dx можно написать кроссплатформенную графическую часть игры(на С++) и затем с минимальными усилиями подключить ее к проекту на любой ОС, поддерживающей данный язык(Android, Symbian, Meego, Bada, про IOS не знаю, но думаю тоже). Конечно, придется столкнуться с особенностями ОС, но усилия будут меньше, чем переписывать код с одного языка на другой и сталкиваться с различиями в порте фреймворка.

Как было



А было все совсем иначе. Код был на Obj-C, пришлось портировать на Android на Java(тогда я еще не знал о хотя бы надежде в виде NDK от хабраюзера crystax). В интернете можно найти два порта Cocos2d-iphone под Android:
  1. Cocos2d-android Хм, ничего толком не скажу про этот проект. Билд от января 2010 года. Пожалуй все!
  2. Cocos2d-android-1 Данный проект утверждает на своей страничке, что проект в пункте 1 развивается слишком медленно, поэтому автор решил создать собственный. Как я понял, был форкнут проект code.google.com/p/cocos2d-android и на его основе пилится версия с приставочкой "-1". Поэтому для работы был выбран именно этот проект.


Немного об архитектуре игры и фреймворка



Фреймворк оперирует слоями и сценами. Честно говоря, до сих пор не понимаю в чем заключается разница между ними. Разве что по логике: сцена может включать в себя несколько слоев. Оставим этот вопрос для комментариев экспертов по данному фреймворку.

Признаюсь, игры мне раньше не доводилось разрабатывать. Был только фан-проект и то замахнулись аж на 3D. Поэтому выбранную архитектуру прокомментировать не смогу.

Игра состояла из набора слоев. Управлением и переходами между слоями управляла одна сцена. Вся игра строилась по принципу синглтона. Существует один объект, который хранит в памяти все настройки игры и загруженные данные об уровнях. Данные об уровнях хранятся в xml. Каждый раз при каждом запуске данный файл парсится и создается синглтон, который все это хранит и доступ к которому есть всегда. В проекте существует только одна активити, которая содержит немного подпиленную вьюшку GLSurfaceView:
public class MainActivity extends Activity {
	private CCGLSurfaceView mGLSurfaceView;
	
	public static float SCREEN_WIDTH = 480; 
	public static float SCREEN_HEIGHT = 320; 
	
        @Override
        public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
       
                requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,		                WindowManager.LayoutParams.FLAG_FULLSCREEN);
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, 
				                  WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

		mGLSurfaceView = new CCGLSurfaceView(this);
		CCDirector director = CCDirector.sharedDirector();
		director.attachInView(mGLSurfaceView);
		director.setDeviceOrientation(CCDirector.kCCDeviceOrientationLandscapeLeft);
		
		SCREEN_WIDTH = director.winSize().width;
		SCREEN_HEIGHT = director.winSize().height;
		
                CCDirector.sharedDirector().getActivity().setContentView(mGLSurfaceView);
		CCDirector.sharedDirector().setDisplayFPS(true);
		CCDirector.sharedDirector().setAnimationInterval(1.0f / 60);
       
		CCScene scene = CCScene.node();
		IntroScene intro = new IntroScene();
		scene.addChild( intro );
		
		CCDirector.sharedDirector().runWithScene(scene);
       }
    
       @Override 
       public void onPause(){
    	      super.onPause();
    	      CCDirector.sharedDirector().onPause();
       }
    
       @Override
	public void onResume() {
		super.onResume();
		CCDirector.sharedDirector().onResume();
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		CCDirector.sharedDirector().end();
	}
	
	@Override
	public void onBackPressed(){
		//показываем диалог или возвращаемся на предыдущий слой
	}
}

Вцелом, типичная архитектура для андроид-приложения и ничего необычного, но в коллбэки добавлены обработчики Cocos2d. Следует отметить, что при использовании 32-битных картинок с градиентом, градиент начинает плыть. Попытки исправить это не к чему не привели. Стандартный способ для андроида(когда переопределяем метод активити onAttachedToWindow и указываем нужную цветовую палитру) не помог. Все исказилось и слетели размеры и цвета. 8-битные картинки тоже не помогли. Хм, такие дела.

Проблемы в порте фреймворка, которые вылезли



Первый трабл был описан в пункте выше, а здесь все оставшиеся.
Слой не отображается на экране. В практике использования данного фреймворка существует такая схема создания сцены:
public class MainScene extends CCLayer {
      public MainScene(){
             //добавляем текстуры, привязываем обработчики кнопок
      }
	
      public static CCScene scene() {
           CCScene scene = CCScene.node();
	   //CCLayer layer = MainScene.node(); //BUG in Cocos
	   CCLayer layer = new MainScene();
	   scene.addChild(layer);
	   return scene;
     }
}

так вот, в версии фреймворка под ios закоменченная строка есть и прекрасно работает. Я нашел решение в виде создания нового экземпляра сцены, но не факт, что это правильно.

Двигаемся дальше.
Возникла необходимость в создании кастомной вьюшки, которая будет при таче красиво перелистывать уровни игры. В IOS-версии это реализовано просто: наследуется класс от UIScrollView и добавляется поверх вьюшки, отрисовывающей графику(GLSurfaceView). У GLSurfaceView есть метод addView(). В порте под андроид такого метода нет, да и вообще пожалуй добавить свою вьюшку нельзя никак. Об этом следует помнить.

Звук. При использовании SoundEngine постоянно выскакивает IllegalStateException, который правда перехватывается. На этом пожалуй все. Хотя нет, это ппц! Создается дерево объектов исключений, оно кидается, перехватывается. Да, какая там производительность игры, о чем вы?

Белая полоса вместо картинки в слое. В игре использовались кастомные диалоги, которые представляли собой слой, добавленный поверх другого слоя. Ничего необычного. Кроме того, что порой, вместо картинок с кнопками меню выскакивала белая полоса. Код один в один как в ios-версии. Ладно бы это проявлялось на слабых устройствах, но эта штука проявляется и на топовых моделях. В issues на гуглокоде есть проблема, связанная со сборщиком мусора. Сдается мне эта относится к ней же, ибо в логах при сравнении появления данной ошибки появлялась строка со сборкой объектов сборщиком мусора. Либо руки кривые у меня(что вполне вероятно), либо порт не учитывает особенностей архитектуры Андроида.

Названия методов и классов фреймворка. Вот что меня удивило так это именование классов и методов в порте. Это веселье просто. Основные классы названы также как и в IOS-версии, но к примеру класс работы со звуком называется иначе(SoundEngine вместо SimpleAudioEngine). Методы потеряли свои приставки, не все конечно, но многие. Сидишь и гадаешь оно или не оно. Смотришь исходники и разбираешься, теряя время. Странное решение выбрал автор, ИМХО.

Поле _rotate или любое другое поле класса. Ничего особенного: полю наследуемого класса присваивается значение угла. Все идентично ios-версии, но спрайт улетал за пределы игрового поля. Несколько часов было потрачено на дебаг и прочие свистопляски. Как оказалось, подобных присваиваний следует избежать. Свойства были заменены на сеттеры и все заработало. Магия портирования да и только!

Работа алгоритма. Очень важный пункт. Логика движения главного персонажа была реализована с помощью акселерометра. Довольно стандартный прием. Но появился серьезный баг, который мешал корректному прохождению уровня. Суть в чем: Cocos2d по тику обновляет данные об игре, графику и прочее. Для этого в классе нужно написать метод: public void update( float delta ). И тут снова вступило различие в архитектуре двух ОС. Из-за задержек связанных со сборкой мусора(а она была частой, ибо много объектов, много текстур) delta была огромной и рушила весь алгоритм, что приводило просто к ужасным последствиям. Опытным путем была подобрана нужна дельта для корректной работы алгоритма и если она больше, то она просто усекалась до нужной.

Заключение



Из основных моментов, это все. Но следует также добавить, что под Андроид портированы не все классы, а многие уже портированные содержат заглушки. По моему мнению, данный фреймворк далеко не лучший кандидат для разработки игр под Андроид. Есть более удачные кандидаты. Хотя, сколько людей — столько и мнений.

Да прибудет с вами сила!

UPD
По просьбе трудящихся указываю БЕСПЛАТНЫЕ фреймворки на которые стоит обратить внимание. Но есть много других. Просто эти содержат больше материалов на StackOverflow и в интернете вцелом.



Про AndEngine помнится уже была серия туториалов на Хабре. Так что самую полезную информацию поможет найти гугл и хабрапоиск.
Tags:
Hubs:
+16
Comments 18
Comments Comments 18

Articles