Pull to refresh

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

Reading time 6 min
Views 38K
Несколько лет делал заказные игрульки под 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(), который устанавливает относительные размеры. Твой айфон, например, имеет размер 960x640 и центр экрана может быть представлен координатой {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
Tags:
Hubs:
+22
Comments 10
Comments Comments 10

Articles