В этом замечательном туториале от Аллана Тана мы создадим собственную игру, похожую на Fruit Ninja от Halfbrick Studios, используя Cocos2D и Box2D.
В большинстве подобных игр, когда игрок разрезает спрайт, тот делится на два заранее подготовленных спрайта; вне зависимости от того, в каком именно месте мы разрубили объект.
Однако в этом туториале мы сделаем вещь покруче. Наши фрукты можно будет резать на несколько кусочков, и резаться они будут в зависимости от того, где прошел палец игрока!
Очевидно, что это руководство не для новичков и требует продвинутых знаний Cocos2D и Box2D. Если вы только начали программировать под iOS, то вам лучше, как минимум, пробежться глазами по введению в Cocos2D и в Box2D.
А вот и видео игры, которую мы создадим, используя несколько крутых приемчиков!
Всех заинтересовавшихся прошу под кат! Warning: очень много переведенного текста!
Обратите внимание: разрез на фрукте появляется там, где игрок проводит пальцем. А так, как фрукты режутся на несколько частей, мы можем покрошить их на кусочки!
Сегодня мы добавим крутой эффект разрезания, систему частиц, логику игры и, конечно же, звук расчленения фруктов.
Этот туториал поделен на три части:
- В первой части мы создадим базу для игры и научимся использовать текстурные полигоны.
- Во второй части мы научимся рубить и разделять текстуры.
- В третьей части мы добавим сам геймплей и веселые эффекты в игру.
Хватит слов — давайте к делу!
Начнем: запускаем проект
Мы будем использовать Cocos2D 2.X, так что загрузите его, если вы еще этого не сделали. Вы можете использовать Cocos2D 1.X, вместо 2.X. С версией 1.X вы можете пропустить шаг адаптирования PRKit и CCBlade для Cocos2D 2.X, но убедитесь, что вы полностью понимаете отличия портированных классов.
После загрузки разархивируйте и установите шаблоны при помощи следующих команд в терминале:
cd ~/Downloads/cocos2d-iphone-2.0-beta
./install-templates.sh -f -u
Запустите Xcode и создайте новый проект iOScocos2d v2.xcocos2d iOS с шаблоном Box2D, назовите его CutCutCut.
Сейчас наш проект должен выглядеть примерно вот так:
Сначала нам нужно очистить шаблон для создания хорошей проектной базы.
Откройте HelloWorldLayer.h и удалите следующую строку:
CCTexture2D *spriteTexture_;// weak ref
Перейдите в HelloWorldLayer.mm и осуществите следующие изменения:
// Удалите эту строку в начале файла
#import "PhysicsSprite.h"
// Замените метод init следующим кодом
- (id)init {
if( (self=[super init])) {
// enable events
self.isTouchEnabled = YES;
self.isAccelerometerEnabled = YES;
CGSize s = [CCDirector sharedDirector].winSize;
// инициализируем физику
[self initPhysics];
[self scheduleUpdate];
}
return self;
}
// Удалите эти два метода
- (void)createMenu {
//содержимое
}
- (void)addNewSpriteAtPosition:(CGPoint)p methods {
//содержимое
}
// Удалите эту строчку из ccTouchesEnded
[self addNewSpriteAtPosition: location];
Мы удалили все отсылки к PhysicsSprite из HelloWorldLayer, но еще не удалили файлы из проекта. Позже нам нужно будет скопировать кое-куда один метод из PhysicsSprite.mm, так что оставим все, как есть.
Нажмите Command+R для запуска проекта, и вы должны увидеть черный экран с зеленой рамкой вокруг:
Оставшийся код запускает дебаг режим: рисует зеленую рамку вокруг всех объектов Box2D на экране. Видите эту зеленую линию вокруг экрана? Это стены, созданные шаблонным методом initPhysics.
Внимательно осмотрите оставшийся код, убедитесь, что вы его полностью понимаете. Мы инициализируем мир Box2D, устанавливаем стены (зеленые линии), зупскаем дебаг режим и т.д. Этот «почти пустой» проект удобен для работы с Box2D.
Пакет ресурсов
Далее скачайте пакет ресурсов для этого проекта и разархивируйте файлы.
Пока ничего не добавляйте в проект; некоторые файлы вам могут вообще не пригодиться. Однако держите папочку под рукой — по мере прохождения туториала мы будем добавлять файлы в проект.
В стартовый набор входит следующее:
- Фоновая каритнка; фрукты, нарисованные Вики; некоторые другие картинки в папке Images
- Фоновая музыка, созданная при помощи gomix.it, в папке Sounds
- Звуковые эффекты, созданные при помощи bfxr или скачанные с freesound, в папке Sounds
- Все системы частиц, созданные при помощи Particle Designer, в папке Particles
- Файл PLIST, созданный при попощи PhysicsEditor, содержащий вершины для класса фруктов и бомб, в папке Misc
- Классы фруктов и бомб в папке Classes
- Версии PRKit и CCBlade в папке Classes
Рисуем текстурные полигоны с PRKit
Наша цель это разрезание спрайтов на несколько частей. Типичный CCSprite содержит текстуру и рамку столкновений (bounding box), никак не зависящую от реальной картинки. Это нас не устраивает, так как нам важно знать реальные размеры картинки для того, чтобы создать спрайты, поддающиеся нарезке, шинковке и разделению.
Нам нужно создать текстурные полигоны, которые:
- Создают соотношение между полигоном/формой и картинкой (Texture Mapping)
- Показывают только те части картинки, которые входят в рамки полигона (Texture Filling)
Ни в Cocos2D, ни в Box2D нет встроенных классов, которые осуществляют необходимые нам действия. Обычно, в таких случаях, лучше написать собственный код прорисовки на OpenGL.
Звучит сложно?
К счастью, все сложные вычисления и нужный код прорисовки уже написан хорошими ребятами из Precognitive Research. Они создали дополнительную Cocos2D библиотеку, которая осуществляет нужные нам действия, и назвали ее PRKit.
Чтобы начать работу с текстурными полигонами, скачайте PRKit, распакуйте его и перенесите папку PRKit folder в ваш проект. Убедитесь, что пункты “Copy items into destination group’s folder” и “Create groups for any added folders” отмечены галочкой.
Заметьте, что PRKit разрабатывается Precognitive Research, так что он часто обновляется. Чтобы избежать различий в версиях, наш набор ресурсов содержит нужную для этого туториала версию PRKit.
Наш проект сейчас должен включать в себя следующие файлы:
Запустите игру, вы обнаружите следующие ошибки:
Ошибки появляются, потому что PRKit был разработан для Cocos2D 1.X, который использует OpenGL ES 1.1, в то время, как мы используем Cocos2D 2.X с OpenGL ES 2.0. А они весомо отличаются друг от друга.
Чтобы это поправить, откройте PRFilledPolygon.m и осуществите следующие изменения:
// Добавьте в метод initWithPoints: andTexture: usingTriangulator:
self.shaderProgram = [[CCShaderCache sharedShaderCache] programForKey:kCCShader_PositionTexture];
// Замените метод calculateTextureCoordinates на следующее
- (void)calculateTextureCoordinates {
for (int j = 0; j < areaTrianglePointCount; j++) {
textureCoordinates[j] = ccpMult(areaTrianglePoints[j],1.0f/texture.pixelsWide*CC_CONTENT_SCALE_FACTOR());
textureCoordinates[j].y = 1 - textureCoordinates[j].y;
}
}
// Замените метод draw на следующее
- (void)draw {
CC_NODE_DRAW_SETUP();
ccGLBindTexture2D( self.texture.name );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
ccGLBlendFunc( blendFunc.src, blendFunc.dst);
ccGLEnableVertexAttribs( kCCVertexAttribFlag_Position | kCCVertexAttribFlag_TexCoords );
glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, areaTrianglePoints);
glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, 0, textureCoordinates);
glDrawArrays(GL_TRIANGLES, 0, areaTrianglePointCount);
}
Давайте шаг за шагом пройдемся по изменениям.
Сначала, каждый CCNode в Cocos2D имеет прикрепленную OpenGL ES 2.0 шейдерную программу. Чтобы нарисовать PRFilledPolygon, нам нужно прикрутить встроенный “Position/Texture” шейдер, который мы реализуем в методе init.
Далее, нам нужно установить правильные текстурные координаты для каждой точки в полигоне. Чтобы это сделать, нам нужно осуществить пару изменений в методе calculateTextureCoordinates:
- Изменить размер: так как этот класс осуществляет собственные вычисления координат текстуры, он не поддерживает дисплеи типа Retina. Чтобы это исправить, мы просто умножаем texture.pixelsWide на CC_CONTENT_SCALE_FACTOR – коэффициент, предоставленный разработчиками Cocos2D для конвертации значений для обычных и Retina-дисплеев.
- Развернуть Y: По какой-то причине, PRFIlledPolygon отрисовывает текстуры вверх ногами, так что мы просто разворачиваем значение y.
В конце концов, мы обновили код для OpenGL ES 2.0. Так же мы изменили прорисовку в CCSprite для Cocos2D 2.X:
- Начинаем с изменения размера с CC_NODE_DRAW_SETUP() для подготовки спрайта для отрисовки.
- Вызовы glDisableClientState() и glEnableClientState() устарели и проигнорированы.
- Обе функции glVertexPointer() и glTexCoordPointer() заменены на glVertexAttribPointer(), которая получает позицию вершины или текстурную координату первым аргументом.
- Запуск функции glTexEnvf(), которая отвечает за повторение картинки, если полигон оказался больше текстуры, заменен вызовами glTexParameteri().
Если вам что-либо непонятно, то вы можете посмотреть туториалы по OpenGL ES 2.0 для iPhone и кастомным шейдерам в Cocos2D 2.X. Но вам не нужно особо об этом волноваться, так как сейчас мы всего лишь портируем классы на Cocos2D 2.X :]
Запустите проект, и ошибки PRKit должны исчезнуть!
Пора начать использовать PRKit. Мы создадим подкласс PolygonSprite класса PRFilledPolygon, который будет отрисовывать наши фрукты.
PolygonSprite построен на основе PRFilledPolygon при помощи добавления тела Box2D в спрайт, добавления некоторых других переменных и методов в имплементацию.
Давайте сделаем это. Нажмите Command+N и создайте новый файл по шаблону iOScocos2d v2.xCCNode. Сделайте его подклассом PRFilledPolygon и назовите PolygonSprite.m.
Переключитесь на PolygonSprite.h и осуществите следующие изменения:
// Добавьте в начале файла
#import "Box2D.h"
#import "PRFilledPolygon.h"
#define PTM_RATIO 32
// Добавьте в @interface
b2Body *_body;
BOOL _original;
b2Vec2 _centroid;
// Добавьте после @interface
@property(nonatomic,assign)b2Body *body;
@property(nonatomic,readwrite)BOOL original;
@property(nonatomic,readwrite)b2Vec2 centroid;
// Добавьте перед @end
- (id)initWithTexture:(CCTexture2D*)texture body:(b2Body*)body original:(BOOL)original;
- (id)initWithFile:(NSString*)filename body:(b2Body*)body original:(BOOL)original;
+ (id)spriteWithFile:(NSString*)filename body:(b2Body*)body original:(BOOL)original;
+ (id)spriteWithTexture:(CCTexture2D*)texture body:(b2Body*)body original:(BOOL)original;
- (id)initWithWorld:(b2World*)world;
+ (id)spriteWithWorld:(b2World*)world;
- (b2Body*)createBodyForWorld:(b2World*)world position:(b2Vec2)position rotation:(float)rotation vertices:(b2Vec2*)vertices vertexCount:(int32)count density:(float)density friction:(float)friction restitution:(float)restitution;
- (void)activateCollisions;
- (void)deactivateCollisions;
Этот код объявляет базовые переменные и методы, которые нам понадобятся для создания PolygonSprite:
- body: Это тело Box2D, прикрепленное к нашему спрайту. Оно необходимо для симуляции физики.
- original: И целые и порезанные кусочки будут объектами класса PolygonSprite, так что нам важно как-то их различать. Если значение YES, то кусок целый, иначе это просто кусок целого.
- centroid: Центр полигона не всегда является центром картинки, так что очень удобно использовать эту переменную.
- properties: Даем доступ к нашим переменным.
- init/spriteWith*: Наши главные инициализирующие методы.
- другие методы: Эти методы создают тела Box2D и оперируют с ними и их переменными.
- PTM_RATIO: Соотносим пиксели и метры. Box2D использует метры вместо пикселей.
Переключитесь на PolygonSprite.m и переименуйте его в PolygonSprite.mm. Все классы, которые содержат одновременно и Objective-C (Cocos2D), и C++ (Box2D) должны иметь расширение “.mm”.
Далее, осуществите следующие изменения в PolygonSprite.mm:
// Добавьте в @implementation
@synthesize body = _body;
@synthesize original = _original;
@synthesize centroid = _centroid;
+ (id)spriteWithFile:(NSString *)filename body:(b2Body *)body original:(BOOL)original {
return [[[self alloc]initWithFile:filename body:body original:original] autorelease];
}
+ (id)spriteWithTexture:(CCTexture2D *)texture body:(b2Body *)body original:(BOOL)original {
return [[[self alloc]initWithTexture:texture body:body original:original] autorelease];
}
+ (id)spriteWithWorld:(b2World *)world {
return [[[self alloc]initWithWorld:world] autorelease];
}
- (id)initWithFile:(NSString*)filename body:(b2Body*)body original:(BOOL)original {
NSAssert(filename != nil, @"Invalid filename for sprite");
CCTexture2D *texture = [[CCTextureCache sharedTextureCache] addImage: filename];
return [self initWithTexture:texture body:body original:original];
}
- (id)initWithTexture:(CCTexture2D*)texture body:(b2Body*)body original:(BOOL)original {
// собираем все вершины из формы Box2D
b2Fixture *originalFixture = body->GetFixtureList();
b2PolygonShape *shape = (b2PolygonShape*)originalFixture->GetShape();
int vertexCount = shape->GetVertexCount();
NSMutableArray *points = [NSMutableArray arrayWithCapacity:vertexCount];
for(int i = 0; i < vertexCount; i++) {
CGPoint p = ccp(shape->GetVertex(i).x * PTM_RATIO, shape->GetVertex(i).y * PTM_RATIO);
[points addObject:[NSValue valueWithCGPoint:p]];
}
if ((self = [super initWithPoints:points andTexture:texture]))
{
_body = body;
_body->SetUserData(self);
_original = original;
// получаем центр полигона
_centroid = self.body->GetLocalCenter();
// ставим якорь (anchor point) на место центра
self.anchorPoint = ccp(_centroid.x * PTM_RATIO / texture.contentSize.width,
_centroid.y * PTM_RATIO / texture.contentSize.height);
// здесь будет больше инициализации, когда мы расширим PolygonSprite
}
return self;
}
- (id)initWithWorld:(b2World *)world {
// ничего тут не делаем
return nil;
}
Прямо как в Cocos2D, все методы spriteWith* это просто autorelease контерчасти методов initWith*. Пока что нет никакого применения методу initWithWorld, но позже мы будем его активно использовать.
Методы initWithFile и initWithTexture содержат несколько изменений. Процесс создания фрукта следует следующему алгоритму:
- initWithWorld: Этот метод пока ничего не возвращает. Мы будем работать с ним позже.
- initWithFile: Этот метод добавляет текстуру из файла и передает ее в метод initWithTexture.
- initWithTexture: Наш главный метод. PRFilledPolygon получает текстуру и вершины полигона. Так, как предыдущий шаг уже обработал текстуру, в этом методе мы будем работать с вершинами: соберем их в одно Box2D тело. После передачи их в PRFillePolygon, мы инициализируем необходимые переменные, которые объявили ранее.
- initWithPoints: Все, что делает этот метод, связано с PRKit. Скорее всего, на больше не нужно будет использовать PRKit.
Все еще PolygonSprite.mm добавьте следующие методы:
- (void)setPosition:(CGPoint)position {
[super setPosition:position];
_body->SetTransform(b2Vec2(position.x/PTM_RATIO,position.y/PTM_RATIO), _body->GetAngle());
}
- (b2Body*)createBodyForWorld:(b2World *)world position:(b2Vec2)position rotation:(float)rotation vertices:(b2Vec2*)vertices vertexCount:(int32)count density:(float)density friction:(float)friction restitution:(float)restitution {
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position = position;
bodyDef.angle = rotation;
b2Body *body = world->CreateBody(&bodyDef);
b2FixtureDef fixtureDef;
fixtureDef.density = density;
fixtureDef.friction = friction;
fixtureDef.restitution = restitution;
fixtureDef.filter.categoryBits = 0;
fixtureDef.filter.maskBits = 0;
b2PolygonShape shape;
shape.Set(vertices, count);
fixtureDef.shape = &shape;
body->CreateFixture(&fixtureDef);
return body;
}
- (void)activateCollisions {
b2Fixture *fixture = _body->GetFixtureList();
b2Filter filter = fixture->GetFilterData();
filter.categoryBits = 0x0001;
filter.maskBits = 0x0001;
fixture->SetFilterData(filter);
}
- (void)deactivateCollisions {
b2Fixture *fixture = _body->GetFixtureList();
b2Filter filter = fixture->GetFilterData();
filter.categoryBits = 0;
filter.maskBits = 0;
fixture->SetFilterData(filter);
}
В этом коде мы сначала загружаем метод setPosition в CCNode так, что когда мы обновляем позицию спрайта, тело Box2D тоже обновляется.
Далее мы создаем метод для создания и объявления тела Box2D. Чтобы создать тело, нам нужно объявить его объект, форму и текстуру. Пока ничего сложного мы не объявиляем, так как этот метод будет использован чуть позже.
Единственное, на что нужно обратить внимание — это categoryBits и maskBits. Мы используем их для фильтрации столкновений между объектами так, что если категория и маска совпадают, то произойдет столкновение между двумя объектами. Мы устанавливаем эти значения в 0, так как нам не нужны столкновения как только объекты инициализируются.
После, мы объявляем два метода, которые просто заменяют categoryBits и maskBits так, что мы можем активировать и деактивировать столкновения наших PolygonSprite когда захотим.
Есть еще одна вещь, которую стоит добавить в PolygonSprite.mm:
-(CGAffineTransform) nodeToParentTransform
{
b2Vec2 pos = _body->GetPosition();
float x = pos.x * PTM_RATIO;
float y = pos.y * PTM_RATIO;
if ( !isRelativeAnchorPoint_ ) {
x += anchorPointInPoints_.x;
y += anchorPointInPoints_.y;
}
// Создаем матрицу
float radians = _body->GetAngle();
float c = cosf(radians);
float s = sinf(radians);
if( ! CGPointEqualToPoint(anchorPointInPoints_, CGPointZero) ){
x += c*-anchorPointInPoints_.x+ -s*-anchorPointInPoints_.y;
y += s*-anchorPointInPoints_.x+ c*-anchorPointInPoints_.y;
}
// Изменяем матрицу
transform_ = CGAffineTransformMake( c, s,
-s,c,
x,y );
return transform_;
}
Помните как я упоминал кое-что о PhysicsSprite? Хорошо, вот и оно. Все, что здесь происходит — проверка на то, что позиция Box2D формы и позиция спрайта совпадают.
После копирования кода выше, вы можете удалить PhysicsSprite.h и PhysicsSprite.mm из проекта, так как мы свели их использование к нулю.
Запустите проект, все должно пройти как по маслу. Мы закончили с PolygonSprite ненадолго.
Размечаем фрукты
Перед созданием класса для наших фруктов, нам нужно полностью определить правила, которым будут следовать наши картинки и их формы. Так как мы будем обрезать наши текстуры до форм полигонов, нам так же нужно добавить некоторые ограничения Box2D полигонов. Нужно держать две вещи в голове:
- Полигоны должны быть выпуклыми, внутренние углы не должны быть больше 180 градусов.
- В полигонах не должно быть больше 8 вершин.
Вообще мы можем работать и вне этих ограничений, например, если наше тело состоит из нескольких форм. Box2D может работать со впуклыми формами, если мы используем треугольный метод и создаем впуклые формы из нескольких треугольников, но это вне темы туториала.
Чтобы максимально упростить туториал, мы будем придерживаться правила: одно тело — одна форма.
Обратите внимание: PhysicsEditor, инструмент, который мы рассмотрим чуть позже, уже содержит код, который автоматически преобразует впуклые формы треугольным методом. Однако мы все еще стараемся максимально упростить туториал, так что будем использовать только выпуклые формы.
Взгляните на эти два фрукта:
Использование банана не очень хорошая идея, потому что он изначально впуклый. Арбуз, наоборот, отличная идея, потому что он выпуклый и очень близко повторяет форму полигона.
Если мы хотим объявить формы полигонов, которые следуют правилам Box2D для обоих фруктов, мы в конце концов придем вот к этому:
Полигоны арбуза идеально подходят к картинке, в то время, как в форме банана есть огромный просвет.
Box2D будет распознавать этот просвет как часть нашего объекта, и столкновения банана с другими предметами, его разрезание будут недостаточно натуральными.
Это не обозначает, что мы не можем использовать банан, просто делать это пока не рекомендуется.
Создаем первый фрукт
Пришло время создать наш первый фрукт: арбуз (по крайней мере, его кусок).
Думая об инициализации нашего объекта класса PolygonSprite, мы вспоминаем, что initWithTexture ожидает тело Box2D, но за шаг до этого, в initWithFile, тела еще нет.
Причина этому в том, что нам нужно создать и объявить тело индивидуально для каждого фрукта, так что это будет самым первым шагом, в initWithWorld, который создает тело и задает ему параметры.
Чтобы создать Box2D тело, мы должны знать вершины формы полигона, которую хотим создать. Есть разные пути решения этой задачи, но в этом туториале мы будем использовать инструмент под именем PhysicsEditor. Этот инструмент просто наполнен разными возможностями, но мы будем его использовать только для получения координат вершин нашего полигона.
Если у вас его еще нет, скачайте PhysicsEditor, установите его и запустите. Вы увидете пустой проект с тремя панелями.
Работа с PhysicsEditor довольно очевидна. Слева у нас все картинки, с которыми мы работаем. Посередине мы визуально определяем полигон для нашей картинки. Справа мы устанавливаем параметры для тела.
Перетяните watermelon.png из папки ресурсов на левую панель. Сейчас вы должны видеть арбуз на центральной панели.
Увеличьте изображение до удобного вам уровня, перетягивая ползунок внизу окна. Нажмите многоугольник вверху этой панели для создания полигона.
Нажмите правой кнопкой мыши по полигону и выберите “Add Vertex” (добавить вершину). Повторяйте, пока у вас не будет 5-8 вершин. Передвиньте вершины вокруг арбуза так, чтобы:
- Полигон оказался выпуклым.
- Все пиксели арбуза были внутри полигона.
Обратите внимание: Другой способ рисования форм, это выбор инструмента «magic wand tool» в PhysicsEditor. Просто установите значение количества вершин на 5-8 и используйте этот инструмент.
Добавьте полигоны ко всем картинкам фруктов и бомбы из папки Images в папке ресурсов.
Вы должны сделать формы следующих картинок:
- banana.png
- bomb.png
- grapes.png
- pineapple.png
- strawberry.png
- watermelon.png
Когда вы закончите, в правом верхнем углу измените значение на Exporter в “Box2D generic (PLIST)”, и у вас должно получиться нечто следующее:
Нажмите «Publish» или «Publish As» для экспортирования файла PLIST с информацией о вершинах. Сохраните этот файл, как fruits.plist.
В качестве примера, fruits.plist, который мы используем в этом туториале, находится внутри папки Misc нашего пакета ресурсов.
Он нам нужен только для того, чтобы понять структуру файла, так что не добавляйте его в проект. Вместо этого откройте его в Xcode и посмотрите на содержимое.
Нажмите на треугольничек около «bodies» для открытия соответствующей секции и вы увидите список картинок, которым мы предали значения форм. Кликните по watermelon для доступа к вершинам арбуза:
Разверните watermelon/fixtures/Item 0/polygons и вы должны увидеть еще одно значение Item 0, являющееся массивом. Этот массив — наша форма. Если вы все правильно сделали, то должны увидеть только один массив.
Если вы видите более чем один массив (напримр: Item 0, Item 1), то вы либо создали более 8 вершин, либо использовали впуклую форму. Если такое произошло, вернитесь в PhysicsEditor и поправьте форму.
Далее, разверните Item 0 чтобы увидеть окончательный список содержимого. Это наши вершины и их значения в формате { x, y }.
Теперь, когда у нас есть точные значения вершин полигона, мы можем продолжить с классом арбуза.
В Xcode создайте новый файл по шаблону iOScocos2d v2.xCCNode Class. Сделайте его подклассом PolygonSprite и назовите его Watermelon. Откройте Watermelon.h и осуществите следующие изменения:
// Добавьте в начало файла
#import "PolygonSprite.h"
Переключитесь на Watermelon.m, переименуйте его в Watermelon.mm, и добавьте следующий метод init:
// добавьте в @implementation
- (id)initWithWorld:(b2World *)world {
int32 count = 7;
NSString *file = @"watermelon.png";
b2Vec2 vertices[] = {
b2Vec2(5.0/PTM_RATIO,15.0/PTM_RATIO),
b2Vec2(18.0/PTM_RATIO,7.0/PTM_RATIO),
b2Vec2(32.0/PTM_RATIO,5.0/PTM_RATIO),
b2Vec2(48.0/PTM_RATIO,7.0/PTM_RATIO),
b2Vec2(60.0/PTM_RATIO,14.0/PTM_RATIO),
b2Vec2(34.0/PTM_RATIO,59.0/PTM_RATIO),
b2Vec2(28.0/PTM_RATIO,59.0/PTM_RATIO)
};
CGSize screen = [[CCDirector sharedDirector] winSize];
b2Body *body = [self createBodyForWorld:world position:b2Vec2(screen.width/2/PTM_RATIO,screen.height/2/PTM_RATIO) rotation:0 vertices:vertices vertexCount:count density:5.0 friction:0.2 restitution:0.2];
if ((self = [super initWithFile:file body:body original:YES]))
{
// Мы инициализируем кое-что еще для фруктов здесь
}
return self;
}
В этом коде мы сначала объявляем, сколько у нас вершин (в нашем случае 7). Далее, мы создаем массив вершин, содержащий все координаты, которые мы только что видели в PLIST файле. Мы используем эту информацию для того, чтобы создать тело, используя метод из PolygonSprite.
Мы добавляем небольшое трение, чтобы объекты не скользили бесконечно и еще немного физики для того, чтобы при столкновении объекты не останавливались, а отталкивались друг от друга.
После, мы создаем объект, вызывая инициализацию суперкласса и передаем ему имя файла картинки, тело Box2D и состояние — фрукт целый.
Нам нужна будет картинка арбуза, так что пришло время добавить всю графику из пакета ресурсов в проект.
В нашем навигаторе проекта, нажмите правой кнопкой мыши и выберите “Add Files to CutCutCut”. Добавьте папку Images из пакета ресурсов в проект. Убедитесь, что пункты “Copy items into destination group’s folder” и “Create groups for any added folders” отмечены галочкой.
Так же добавьте банан, ананас, клубнику и бомбу.
Пока что мы добавили только один фрукт для примера. Пакет ресурсов включает в себя классы остальных фруктов, кторыми вы можете руководствоваться при написании собственных, или можете просто добавить их в проект, если хотите пропустить этот шаг.
Запустите свой проект и убедитесь, что ошибок нет.
Добавляем фрукт на экран
Пока что на экране ничего не происходит, а мы-то хотим увидеть фрукты на любой вкус и цвет! :]
Переключитесь на HelloWorldLayer.h и осуществите следующие изменения:
// Добавьте в начало файла
#import "PolygonSprite.h"
// Добвыьте в @interface
CCArray *_cache;
// Добавьте после @interface
@property(nonatomic,retain)CCArray *cache;
Switch back to HelloWorldLayer.mm and make these changes:
// добавьте в начало файла
#import "Watermelon.h"
// добавьте в @implementation
@synthesize cache = _cache;
// добавьте в метод init, под [self initPhysics]
[self initSprites];
// добавьте в метод dealloc, перед вызовом [super dealloc]
[_cache release];
_cache = nil;
// добавьте где-нибудь в @implementation перед @end
- (void)initSprites {
_cache = [[CCArray alloc] initWithCapacity:53];
// пока создаем только один спрайт. Чуть позже мы перепишем весь метод.
PolygonSprite *sprite = [[Watermelon alloc] initWithWorld:world];
[self addChild:sprite z:1];
[sprite activateCollisions];
[_cache addObject:sprite];
}
Мы объявляем кэш-массив, который будет содержать в себе все фрукты и бомбы, которые мы создадим в будущем. Далее, мы создаем один арбуз и добавляем его на экран. Мы вызываем activeteCollisions, так что арбуз не проходит сквозь стены.
Запустите проект, вы должны увидеть, как арбуз падает с центра экрана вниз и приземляется на нижнюю грань экрана.
Вы могли заметить, что арбуз располагается не точно по центру. Причина этому в том, что мы позиционировали объект на основе его Box2D тела, а начало этого тела находится в левом нижнем углу спрайта. Видно тонкую обводку объекта, потому что включен дебаг мод.
Что дальше?
А вот и проект со всем кодом из этого туториала.
Мы закончили первую часть из серии туториалов. Пока что мы сделали полигон для Арбуза, который падает вниз.
Но вместо отрисовки прямоугольного спрайта с прозрачными частями, как обычно делается в туториалах по Box2D, мы использовали PRKit для отрисовки только той части текстуры, которая входит в тело объекта Box2D. Скоро нам это сильно поможет!
Теперь мы готовы ко второй части туториала, где мы научимся резать фрукты на кусочки!
Примечание переводчика
Продолжаю переводить туториалы с сайта raywenderlich.com.
Всем разработчикам игр под iOS крайне советую этот сайт!
Если переводы следующих частей будут востребованы, переведу.
Обо всех найденных неточностях и опечатках, пожалуйста, пишите в хабрапочте или тут в комментариях.
С радостью отвечу на все вопросы по туториалу!
Автор: backmeupplz