Cocos2D-X и чтобы легко на всех устройствах

от автора

Пост опубликован по просьбе пользователя reenboog. Кармы не хватает.

Несколько лет делал заказные игрульки под iOS. В условиях, когда некогда точить, а нужно пилить, идешь в гугл и спрашиваешь. Гуглокодинг. Вот и свела судьба меня с Cocos2D for iPhone и теплым ламповым www.raywenderlich.com

Мне Objective-C понравился, как и сам cocos2D. Мягкий как пластилин. После приличных лет писанины на C++ все как-то упростилось. Увы, только iOS. Безусловно, появились всякие Apportable, однако я не хотел почему-то смотреть в ту сторону. К тому же чувствовалась усталость от одной и той же платформы и хотелось своего проекта, при том, чтобы игралось на каждой микроволновке. Unity вроде хорош, но закрыт, а для меня очень важно знать, как оно работает изнутри: оценить потенциальные боттлнеки, что-то оптимизировать (приходилось часто за практику), да даже просто баги пофиксить. Плюс, хотелось начать что-то делать прям сейчас. А поскольку с моделью айфоновского кокоса я был очень хорошо знаком, было принято решение взглянуть на cocos2D-X. Тот, что на C++.

То же самое. Просто на С++. Те же release/retain (в последней 3.x версии некоторые моменты уже изменились), та же модель из нод. Погонял тестовый стенд (всегда, всегда смотрите примеры работы движка) — все шустро работает. Вспомнил я, правда, про одну штуку — про Android с его множеством разрешений экранов.

Когда я делал игры под айфоны, все было просто. Если нужна была поддержка ретины, то достаточно было установить лишь один флаг:

[director enableRetinaDisplay: YES] 

и добавить набор HD-графики. Под айпэды все было слегка иначе: требовался некоторый перерасчет координат. С появлением пятых айфонов все чуть усложнилось, но немножко. Однако, когда ты Андроид и у тебя почти неограниченное количество всяких экранов, нужно немного включить мозг. Вот, что мы сделали.

Мы добавили конфиги. Вся магия твоего проекта начинает происходить в сгенерированном кокосовским скриптом классе AppDelegate. В одном из его методов запускается и активируется сцена:

bool AppDelegate::applicationDidFinishLaunching() { 	Director *director = Director::getInstance();     EGLView *eglView = EGLView::getInstance();      director->setOpenGLView(eglView);      	... 	 	Scene *scene = GameScene::scene();      director->runWithScene(scene);     return true;  } 

В сцене ты грузишь свои спрайты/иные ноды, как-то их позиционируешь, крутишь, если потребуется, и т.д. Механизм создания, например, спрайта тесно связан с классом FileUtils. Всякий раз, когда ты творишь что-то вроде:

Sprite *object = Sprite::create("object.png") 

внутренние методы (хорошо иметь код под рукой) класса Sprite просят FileUtils побегать по списку установленных путей поиска и посмотреть, есть ли там такой-то файл. Пути поиска? Да это просто. Можно создавать спрайты так:

Sprite *enemy = Sprite::create("enemies/enemy0.png") 

а можно сказать кокосу, что у нас есть некоторая директория или даже список, в которых следует искать файлы. Это сокращает писанину, соответственно, уменьшает количество ошибок и придает гибкости. Захотел сгруппировать файлы иным образом — пожалуйста, в коде не придется менять все «enemies/» на, к примеру, «objects/». Действительно удобнои легко:

FileUtils::getInstance()->addSearchPath("fonts"); FileUtils::getInstance()->addSearchPath("objects"); FileUtils::getInstance()->addSearchPath("backgrounds");  ...  Sprite *back = Sprite::create("back0.png"); LabelBMFont *label = LabelBMFont::create("an incredible label", "font0.fnt"); 

Что-то я, кажется, говорил про всякие разрешения экранов. В cocos2D-X уже нет всяких

[director enableRetinaDisplay: YES] 

вместо этого появилась пара новых вещей. Хочешь узнать физический размер экрана? Сделай так:

Size frameSize = Director::getInstance()->getOpenGLView()->getFrameSize(); 

Еще я что-то говорил про конфиг. Это простой json-файл (парсим rapidson), в котором мы прописали наборы путей ресурсов (ох, этот винительный падеж). Каждый набор выглядит так:

{ 	"width": 960, 	"height": 640, 	"designWidth": 480, 	"designHeight": 320, 	"paths": 	[ 	    "Res/960x640/fonts/", 	    "Res/960x640/ui/", 		"Res/960x640/maps/" 		... 	] } 

Здесь width и height представляют физические размеры, для которого этот набор ресурсов мог бы подойти. Уже улавливаешь, к чему я клоню? При старте приложения в AppDelegate::applicationDidFinishLaunching я загружаю конфиг, бегаю по всем наборам путей и сверяю физический размер, полученный из Director::getInstance()->getOpenGLView()->getFrameSize(), с теми width и height и загружаю нужных размеров картинки. Этого, однако, недостаточно.

Для более глубокого понимания сходи сюда:
www.cocos2d-x.org/wiki/Multi_resolution_support

Для практического же использования достаточно знать следующее. Есть Director::getInstance()->getOpenGLView()->setDesignResolutionSize(), который устанавливает относительные размеры. Твой айфон, например, имеет размер 960×640 и центр экрана может быть представлен координатой {480, 320}. Однако, можно передать в setDesignResolutionSize какие-нибудь {96, 64}, и тогда центр можно будет задать с помощью {48, 32}. Относительные координаты очень важны, а те два параметра designWidth и designHeight из конфига их и выставляют. Итак, мы бегаем по конфигу, сверяем размеры, грузим нужные ресурсы и устанавливаем правильные относительные координаты. Мы почти на финишной прямой.

Представь, что ты запускаешь игру на устройстве с огромным разрешением экрана, но набора ресурсов, чтобы прям pixel perfect, нет. Ничего страшного — мы просто скажем кокосу, чтобы он растянул картинку. Для этого есть Director::getInstance()->setContentScaleFactor(), принимающий некоторый float. Просто подели (в случае, если игра портретная) width из конфига на designWidth и будет тебе счастье на всех платформах.

Есть еще один параметр, последний — ResolutionPolicy. Если честно, то полезных там два штуки: ResolutionPolicy::FIXED_WIDTH и ResolutionPolicy::FIXED_HEIGHT. Хочешь играть в портретной ориентации — ставь ResolutionPolicy::FIXED_WIDTH. При этом, картинка растянется по ширине, например, как в моей игре solve Me:

При таком подходе может появиться дополнительно вертикальное пространство. В примере выше я просто растягивал фон так, чтобы замостился весь экран, а элементы интерфейса располагал относительно его краев. Однако, это работает не всегда. Так, в другой моей игре reTales с ландшафтной ориентацией (использовался ResolutionPolicy::FIXED_HEIGHT) я просто рисовал два спрайта по краям экрана:

Бегаем по конфигу, считываем наборы путей, сравниваемся с физическими размерами, грузим нужные ресурсы, применяем верную политику и растягиваемся. Но я пошел еще чуть дальше — прикрутил локализацию. На уровне движка. Особенность FileUtils::getInstance()->addSearchPath() в том, ресурсы будут искаться строго по тем путям, которые были установлены, и в том же порядке. Не нашел в «fonts/», идет в «ui/», не нашел там — идет в «maps/» и далее ищет в корне в случае неудачи. Прекрасно. Мы ведь можем получить текущий язык с помощью Application::getInstance()->getCurrentLanguage() и в момент добавления пути добавить сначала путь, характерный для конкретного языка, а потом и сам этот путь. Лучше один раз увидеть. Будет происходить примерно следующее:

FileUtils::getInstance()->addSearchPath("fonts/en"); FileUtils::getInstance()->addSearchPath("fonts");  FileUtils::getInstance()->addSearchPath("objects/en"); FileUtils::getInstance()->addSearchPath("objects");  FileUtils::getInstance()->addSearchPath("backgrounds/en"); FileUtils::getInstance()->addSearchPath("backgrounds"); 

В итоге при попытке создать спрайт кокос сначала посмотрит в локализованную папку, потом в папку общую. Нужно, чтобы шрифты грузились в зависимости от языка по-умолчанию? Просто создай папку. Нужно, чтобы конкретные спрайты (флаг на нашивке солдата) грузились в соответствии с языком игрока? Создай папку и кинь туда нужный файл. Сам текст просто лежит в таких же json-файлах как и конфиг. Вместо

Label::create("This is label", "font.fnt")  

просто используй

Label::create(Localized::getString("mainMenuCaptionLabel"), "font.fnt") 

Класс Localized и многое другое ты сможешь скачать по ссылкам ниже. Мне не жалко 🙂

Спасибо, чтобы был со мной, читатель. Не забывай кормить своего кота — он очень в тебе нуждается.

Пример рабочего конфига — pastebin.com/5idCpjYh
Пример файла со строками — pastebin.com/LfBxs6dA
Localized.h — pastebin.com/LwNaKrFK
Localized.cpp — pastebin.com/GHVmPvCc
Загрузка путей — pastebin.com/CX8Xma25
Как запустить все это дело в AppDelegate — pastebin.com/dMVtY2Rb

ссылка на оригинал статьи http://habrahabr.ru/post/217085/


Комментарии

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

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