Это третий выпуск нашей серии руководств по Cocos2D по клонированию сороконожки для iOS. В сегодняшнем уроке мы начнем кодировать объект и движение Caterpillar. Убедитесь, что вы завершили предыдущие части, прежде чем начать.
Где мы остановились
В последнем уроке я показал вам, как построить поле Sprout для игровой зоны. Вы также узнали, как создавать некоторые базовые элементы пользовательского интерфейса, используя Cocos2D.
Сегодня мы испачкаем руки и создадим основной объект в игре: Caterpillar. Caterpillar — самый сложный объект в нашей серии, и нам понадобятся две части, чтобы полностью его покрыть. Это будет первая часть, где мы инициализируем гусеницу со всеми ее сегментами и заставляем ее перемещаться по экрану. Следующая часть будет более глубокой и будет посвящена искусственному интеллекту, который управляет гусеницей.
Шаг 1: Объект Гусеницы
Объект Caterpillar будет самым сложным из объектов, которые мы создадим в этом уроке. Он добавит многочисленные свойства в наш базовый класс GameObject, а также несколько новых методов инициализации.
Начните с создания нового файла, который является подклассом GameObject и называется Caterpillar. Вставьте следующий код в Caterpillar.h:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
#import «cocos2d.h»
#import «GameObject.h»
@class Segment;
// 1
typedef enum {
CSRight,
CSLeft,
CSUpLeft,
CSUpRight,
CSDownLeft,
CSDownRight
} CaterpillarState;
@interface Caterpillar : GameObject
// 2
@property (nonatomic, retain) NSMutableArray *segments;
// 3
@property (nonatomic, assign) CaterpillarState currentState;
@property (nonatomic, assign) CaterpillarState previousState;
// 4
@property (nonatomic, assign) ccTime totalTime;
@property (nonatomic, assign) NSInteger moveCount;
@property (nonatomic, assign) NSInteger level;
// 5
— (id)initWithGameLayer:(GameLayer *)layer level:(NSInteger)level position:(CGPoint) position;
// 6
— (void)update:(ccTime)dt;
@end
|
Прежде чем мы обсудим этот код, я хочу поговорить с вами об общей архитектуре гусеницы. Я решил реализовать его как конечный автомат . Это означает, что поведение Caterpillar полностью зависит от его состояния. События в игре (такие как столкновения) изменят состояние Гусеницы, тем самым изменив ее поведение. Для наших целей нам нужно знать о его текущем состоянии, а также о его предыдущем состоянии.
- Это все возможные состояния, которые может иметь гусеница. Они должны быть достаточно очевидными, основываясь на их именах. Если вы не знаете, что такое enum, я предлагаю вам прочитать об этом .
- Наша гусеница на самом деле представляет собой массив объектов Сегмент, где каждый сегмент следует за одним перед ним.
- Состояния гусеницы используются для определения ее поведения.
- Некоторые вспомогательные свойства, которые мы будем использовать в классе. Я более подробно расскажу о каждом из них, когда мы их используем.
- Новый метод init, который также учитывает положение и уровень во время инициализации.
- Поскольку наша гусеница будет анимирована, нам нужно подключиться к основному циклу игры. Метод обновления — это то, что мы будем вызывать из метода обновления GameLayer, который мы вскоре внедрим.
Теперь откройте Caterpillar.m и добавьте следующий код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
#import «Caterpillar.h»
#import «GameLayer.h»
#import «GameConfig.h»
@implementation Caterpillar
@synthesize segments = _segments;
@synthesize currentState = _currentState;
@synthesize previousState = _previousState;
@synthesize totalTime = _totalTime;
@synthesize moveCount = _moveCount;
@synthesize level = _level;
— (void)dealloc {
[_segments release];
[super dealloc];
}
— (id)initWithGameLayer:(GameLayer *)layer level:(NSInteger)level position:(CGPoint) position {
if(self = [super initWithGameLayer:layer]) {
self.segments = [NSMutableArray array];
self.level = level;
self.currentState = CSRight;
self.previousState = CSDownLeft;
// 1
[super setPosition:position];
// 2
int length = kCaterpillarLength + self.level / 2;
}
return self;
}
@end
|
Пока это не выглядит слишком сложным.
- Мы будем перезаписывать метод
setPostion:
в этом классе через некоторое время, поэтому нам нужно явно вызвать супер метод, чтобы не вызывать наш метод локального сеттера. (делая self.position). - Длина гусеницы будет зависеть от текущего уровня игры. Чем дальше игрок, тем длиннее будет гусеница.
Мы добавили kCaterpillarLength
конфигурационную переменную под названием kCaterpillarLength
. Откройте GameConfig.h и добавьте эту строку:
1
|
#define kCaterpillarLength 11
|
Это просто говорит о том, что начальная длина гусеницы составляет 11 сегментов.
Шаг 2: Сегментный объект
Как я уже говорил, гусеница — это просто серия сегментов. Итак, давайте реализуем этот объект, и мы вновь вернемся к методу init Caterpillar, чтобы на мгновение настроить их.
Создайте новый подкласс GameObject с именем Segment. Вставьте следующий код в Segment.h:
1
2
3
4
5
6
7
8
9
|
#import «cocos2d.h»
#import «GameObject.h»
@interface Segment : GameObject
@property (nonatomic, assign) CGPoint previousPosition;
@property (nonatomic, assign) Segment *parent;
@end
|
Главное, что следует отметить в отношении объектов сегмента, это то, что им нужно знать своего родителя И свою предыдущую позицию. «Голова» гусеницы будет обозначена сегментом с нулевым родителем. Вы могли бы почти думать о сегментах как связанный список.
Теперь откройте Segment.m и добавьте следующий код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#import «Segment.h»
#import «GameLayer.h»
@implementation Segment
@synthesize previousPosition = _previousPosition;
@synthesize parent = _parent;
— (id)initWithGameLayer:(GameLayer *)layer {
if(self = [super initWithGameLayer:layer]) {
self.sprite = [CCSprite spriteWithSpriteFrameName:@»segment.png»];
[self.gameLayer.spritesBatchNode addChild:self.sprite];
}
return self;
}
— (void) setPosition:(CGPoint)position {
_previousPosition = self.position;
[super setPosition:position];
}
@end
|
Метод initWithGameLayer
здесь не должен быть новым. Единственное дополнение к этому классу — мы должны переопределить setPostion
, чтобы отслеживать предыдущую позицию. Поскольку мы переопределяем метод setPostion
, нам нужно обязательно вызвать версию суперкласса, чтобы наш спрайт перемещался.
Вот и все для сегмента, теперь давайте вернемся к методу init в Caterpillar и добавим немного кода для создания его сегментов.
Шаг 3: Создание сегментов
Откройте Caterpillar.m, импортируйте Segment.h и добавьте следующий код в ваш метод initWithGameLayer
внутри основного оператора if
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
for(int i = 0; i < length; i++) {
// 1
Segment *segment = [[[Segment alloc] initWithGameLayer:self.gameLayer] autorelease];
segment.position = position;
[self.segments addObject:segment];
}
__block Segment *parentSegment = [self.segments objectAtIndex:0];
// 2
[self.segments enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Segment *segment = (Segment *)obj;
if(![segment isEqual:parentSegment]) {
segment.parent = parentSegment;
}
segment.position = self.position;
segment.previousPosition = self.position;
parentSegment = segment;
}];
|
- Мы начинаем с создания новых сегментов и добавления их в массив сегментов. Это делается по
length
раз. Кроме того, их положение устанавливается в исходное положение гусеницы. - Каждый сегмент перечисляется, а его родитель устанавливается на предыдущий сегмент. Там есть проверка, чтобы убедиться, что родительский сегмент остается нулевым.
Шаг 4: Инициализация гусеницы
Хотя игра начинается с одной гусеницы, она становится множественной гусеницей после удара игрока. При этом ваш GameLayer должен поддерживать неограниченное количество объектов Caterpillar.
Откройте GameLayer.h и добавьте следующее свойство:
1
|
@property (nonatomic, retain) NSMutableArray *caterpillars;
|
Теперь откройте GameLayer.m, импортируйте Caterpillar.h и добавьте следующий код в ваш метод init:
1
2
3
4
|
_caterpillars = [[NSMutableArray alloc] init];
CGPoint startingPosition = ccp(kGameAreaStartX, kGameAreaHeight + kGameAreaStartY — kGridCellSize / 2);
Caterpillar *caterpillar = [[[Caterpillar alloc] initWithGameLayer:self level:self.level position:startingPosition] autorelease];
[self.caterpillars addObject:caterpillar];
|
Это создаст один объект Caterpillar в начальной точке и добавит его в массив гусениц.
Шаг 5: Перемещение Гусеницы
В Cocos2D вы можете подключиться к основному игровому циклу, запланировав вызов одного из ваших методов на каждом тике.
Откройте GameLayer.m и добавьте следующий код в ваш метод init:
1
|
[self schedule:@selector(update:)];
|
Это говорит Cocos2D вызывать метод с именем update:
на каждом такте игрового цикла. Теперь давайте реализуем этот метод обновления, добавив следующий код:
1
2
3
4
5
6
|
— (void)update:(ccTime)dt {
[self.caterpillars enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Caterpillar *caterpillar = (Caterpillar *)obj;
[caterpillar update:dt];
}];
}
|
В этом коде мы просто перечисляем все объекты Caterpillar и вызываем их методы обновления. Гусеница возьмет это отсюда.
Давайте вернемся к Caterpillar.m и добавим следующее update:
метод:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
— (void)update:(ccTime)dt {
// 1
self.totalTime += dt;
if(self.totalTime < 4.0 / (self.level * 2.0)) {
self.totalTime += dt;
return;
} else {
self.totalTime = 0;
}
// 2
__block int x = self.position.x;
__block int y = self.position.y;
// 3
x += kGridCellSize;
// 4
self.position = ccp(x,y);
}
|
- Поскольку мы перемещаем гусеницу на одинаковое количество пикселей на каждом тике (16), нам нужен какой-то другой способ управления ее скоростью. Решение состоит в том, чтобы ограничить частоту запуска этого метода на основе текущего уровня. При повышении уровня этот метод разрешается запускать чаще.
- Нам нужно локализовать текущее положение гусеницы, чтобы изменить эти переменные. Я пометил их как тип
__block
как они будут изменены внутри блока в следующем уроке. - Это увеличение является временным и предназначено только для демонстрации базового движения в целях данного урока. В конце концов мы заменим это чем-то более сложным. Увеличение х здесь будет просто перемещать гусеницу вправо, пока она не сойдет с экрана.
- Наконец, положение гусеницы обновляется после того, как мы настроили переменные местоположения.
Последний шаг для перемещения этой гусеницы — переопределить метод setPosition
. Добавьте следующий код в Caterpillar.m:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
— (void) setPosition:(CGPoint)position {
// 1
[super setPosition:position];
// 2
Segment *head = [self.segments objectAtIndex:0];
head.position = position;
// 3
[self.segments enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Segment *segment = (Segment *)obj;
if(segment.parent) {
segment.position = segment.parent.previousPosition;
}
}];
}
|
- Первым шагом здесь является вызов метода super, чтобы все правильно обновлялось в родительском элементе.
- Перемещение гусеницы в основном означает перемещение ее головы. Все остальное должно просто следовать.
- Мы перечисляем сегменты и устанавливаем их текущие позиции на предыдущие позиции их родителей. Это достигнет поведения «следовать».
Теперь запустите ваш код, и вы увидите гусеницу, движущуюся по верху игровой зоны.
Вывод
Теперь у вас есть движущаяся гусеница в Cocos2D. В настоящее время вы заметите, что гусеница просто движется прямо с экрана, когда приближается к краю. Это будет решено в следующий раз.
В следующий раз
В следующем уроке из этой серии мы рассмотрим, как гусеница взаимодействует с каждой из стен, а также с ростками. Я покажу вам, как столкновение изменит состояние гусеницы и заставит ее реагировать.