Статьи

Создайте игру Caterpillar с Cocos2D: создание Caterpillar

Это третий выпуск нашей серии руководств по Cocos2D по клонированию сороконожки для iOS. В сегодняшнем уроке мы начнем кодировать объект и движение Caterpillar. Убедитесь, что вы завершили предыдущие части, прежде чем начать.


В последнем уроке я показал вам, как построить поле Sprout для игровой зоны. Вы также узнали, как создавать некоторые базовые элементы пользовательского интерфейса, используя Cocos2D.

Сегодня мы испачкаем руки и создадим основной объект в игре: Caterpillar. Caterpillar — самый сложный объект в нашей серии, и нам понадобятся две части, чтобы полностью его покрыть. Это будет первая часть, где мы инициализируем гусеницу со всеми ее сегментами и заставляем ее перемещаться по экрану. Следующая часть будет более глубокой и будет посвящена искусственному интеллекту, который управляет гусеницей.


Объект 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 полностью зависит от его состояния. События в игре (такие как столкновения) изменят состояние Гусеницы, тем самым изменив ее поведение. Для наших целей нам нужно знать о его текущем состоянии, а также о его предыдущем состоянии.

  1. Это все возможные состояния, которые может иметь гусеница. Они должны быть достаточно очевидными, основываясь на их именах. Если вы не знаете, что такое enum, я предлагаю вам прочитать об этом .
  2. Наша гусеница на самом деле представляет собой массив объектов Сегмент, где каждый сегмент следует за одним перед ним.
  3. Состояния гусеницы используются для определения ее поведения.
  4. Некоторые вспомогательные свойства, которые мы будем использовать в классе. Я более подробно расскажу о каждом из них, когда мы их используем.
  5. Новый метод init, который также учитывает положение и уровень во время инициализации.
  6. Поскольку наша гусеница будет анимирована, нам нужно подключиться к основному циклу игры. Метод обновления — это то, что мы будем вызывать из метода обновления 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

Пока это не выглядит слишком сложным.

  1. Мы будем перезаписывать метод setPostion: в этом классе через некоторое время, поэтому нам нужно явно вызвать супер метод, чтобы не вызывать наш метод локального сеттера. (делая self.position).
  2. Длина гусеницы будет зависеть от текущего уровня игры. Чем дальше игрок, тем длиннее будет гусеница.

Мы добавили kCaterpillarLength конфигурационную переменную под названием kCaterpillarLength . Откройте GameConfig.h и добавьте эту строку:

1
#define kCaterpillarLength 11

Это просто говорит о том, что начальная длина гусеницы составляет 11 сегментов.


Как я уже говорил, гусеница — это просто серия сегментов. Итак, давайте реализуем этот объект, и мы вновь вернемся к методу 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 и добавим немного кода для создания его сегментов.


Откройте 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;
 
}];
  1. Мы начинаем с создания новых сегментов и добавления их в массив сегментов. Это делается по length раз. Кроме того, их положение устанавливается в исходное положение гусеницы.
  2. Каждый сегмент перечисляется, а его родитель устанавливается на предыдущий сегмент. Там есть проверка, чтобы убедиться, что родительский сегмент остается нулевым.

Хотя игра начинается с одной гусеницы, она становится множественной гусеницей после удара игрока. При этом ваш 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 в начальной точке и добавит его в массив гусениц.


В 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);
 
}
  1. Поскольку мы перемещаем гусеницу на одинаковое количество пикселей на каждом тике (16), нам нужен какой-то другой способ управления ее скоростью. Решение состоит в том, чтобы ограничить частоту запуска этого метода на основе текущего уровня. При повышении уровня этот метод разрешается запускать чаще.
  2. Нам нужно локализовать текущее положение гусеницы, чтобы изменить эти переменные. Я пометил их как тип __block как они будут изменены внутри блока в следующем уроке.
  3. Это увеличение является временным и предназначено только для демонстрации базового движения в целях данного урока. В конце концов мы заменим это чем-то более сложным. Увеличение х здесь будет просто перемещать гусеницу вправо, пока она не сойдет с экрана.
  4. Наконец, положение гусеницы обновляется после того, как мы настроили переменные местоположения.

Последний шаг для перемещения этой гусеницы — переопределить метод 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;
        }
    }];
}
  1. Первым шагом здесь является вызов метода super, чтобы все правильно обновлялось в родительском элементе.
  2. Перемещение гусеницы в основном означает перемещение ее головы. Все остальное должно просто следовать.
  3. Мы перечисляем сегменты и устанавливаем их текущие позиции на предыдущие позиции их родителей. Это достигнет поведения «следовать».

Теперь запустите ваш код, и вы увидите гусеницу, движущуюся по верху игровой зоны.

гусеница

Теперь у вас есть движущаяся гусеница в Cocos2D. В настоящее время вы заметите, что гусеница просто движется прямо с экрана, когда приближается к краю. Это будет решено в следующий раз.


В следующем уроке из этой серии мы рассмотрим, как гусеница взаимодействует с каждой из стен, а также с ростками. Я покажу вам, как столкновение изменит состояние гусеницы и заставит ее реагировать.