Это шестая часть нашей серии руководств по Cocos2D по клонированию сороконожки для iOS. Убедитесь, что вы завершили предыдущие части, прежде чем начать.
Последний раз…
В последнем уроке я показал вам, как создать массив ракетных объектов и запустить их постоянный поток. Вы также узнали об основных взаимодействиях с игроком в Cocos2D для перемещения игрока.
В сегодняшнем уроке мы рассмотрим, как настроить базовое обнаружение столкновений в Cocos2D. Хотя это не всегда оптимальное решение, оно определенно является самым быстрым и простым в реализации.
Шаг 1: Ракета / столкновение Ростка
Столкновение ракеты с ростками очень похоже на столкновение игрока с ростками. Мы просто проверяем границы каждой ракеты в игре против границ каждого ростка в игре и определяем, сталкиваются ли они. Когда прорастают, мы уменьшаем его жизнь, изменяем его непрозрачность и удаляем его, если его жизнь достигает 0.
Откройте Missile.m, импортируйте Sprout.h и добавьте следующий код в конец метода обновления:
1
|
CGRect missileRect = [self getBounds];
|
По мере обновления каждой ракеты она перечисляет все побеги в игре, проверяя, пересекаются ли их ограничивающие стороны. Если происходит столкновение, мы устанавливаем ракету на «грязную» и соответственно уменьшаем жизнь ростка. Мы уже написали код для изменения непрозрачности ростков в зависимости от их жизни, поэтому, если вы запустите игру в этот момент, вы должны увидеть, как ростки меняются при попадании. Проблема в том, что они все еще в игре. Нам нужно удалить ростки с 0 жизнями.
Это можно сделать внутри метода update:
в GameLayer.m. Откройте GameLayer.m и добавьте следующий код в конец update:
метод:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
// 1
__block Sprout *deadSprout = nil;
[self.sprouts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Sprout *sprout = (Sprout *)obj;
if(sprout.lives == 0) {
deadSprout = sprout;
*stop = YES;
}
}];
// 2
if(deadSprout) {
[self.spritesBatchNode removeChild:deadSprout.sprite cleanup:YES];
[self.sprouts removeObject:deadSprout];
}
|
- Перечислим каждый из ростков в поисках мертвого. Для упрощения мы очищаем только один росток за итерацию.
- Если мертвый росток существует, мы удаляем его спрайт из пакетного узла и удаляем росток из нашего массива ростков.
Теперь запустите игру, и вы увидите, как ростки исчезают при попадании и в конечном итоге исчезают.
Шаг 2: Столкновение Гусеницы
Теперь нам нужно добавить обнаружение столкновений между ракетой и гусеницей. Именно это взаимодействие делает ваше приложение игрой. После попадания гусеница должна расколоться на участке удара, и каждая новая «гусеница» должна двигаться в разных направлениях. Сегмент, который был поражен, затем превращается в росток.
Для начала откройте Missile.m, импортируйте Caterpillar.h и Segment.h и добавьте следующий код в конец update:
метод:
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
|
__block Caterpillar *hitCaterpillar = nil;
__block Segment *hitSegment = nil;
// 1
[self.gameLayer.caterpillars enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Caterpillar *caterpillar = (Caterpillar *)obj;
[caterpillar.segments enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Segment *segment = (Segment *)obj;
CGRect segmentRect = [segment getBounds];
// 2
if(CGRectIntersectsRect(missileRect, segmentRect)) {
self.dirty = YES;
hitCaterpillar = [caterpillar retain];
hitSegment = [segment retain];
*stop = YES;
}
}];
}];
// 3
if(hitCaterpillar && hitSegment) {
[self.gameLayer splitCaterpillar:hitCaterpillar atSegment:hitSegment];
[hitSegment release];
[hitCaterpillar release];
}
|
- Перечислите всех гусениц в игре и перечислите все их сегменты.
- Проверьте на столкновение и помните, какой сегмент какой гусеницы был поражен.
- Если у нас есть удар, мы разделяем гусеницу на текущий сегмент. Не волнуйтесь, мы вскоре реализуем этот метод.
Теперь, когда у нас ракета сталкивается с гусеницей, нам нужно сделать несколько вещей. Первый — написать логику для разделения гусеницы на данный сегмент. Это будет сделано в GameLayer.m. Сначала откройте GameLayer.h и добавьте следующие объявления класса forward.
1
2
|
@class Caterpillar;
@class Segment;
|
Затем объявите следующий метод:
1
|
— (void)splitCaterpillar:(Caterpillar *) caterpillar atSegment:(Segment *)segment;
|
Прежде чем мы начнем с реализации, нам нужно добавить два файла в проект. Скачайте NSArray + Reverse , разархивируйте его и перетащите оба файла в свой проект. Это просто категория в NSMutableArray, которая дает нам обратный метод. Теперь откройте GameLayer.m, импортируйте Segment.h и NSArray + Reverse.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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
— (void)splitCaterpillar:(Caterpillar *)caterpillar atSegment:(Segment *)segment
{
// 1
if([caterpillar.segments count] == 1) {
[self.spritesBatchNode removeChild:segment.sprite cleanup:NO];
[self.caterpillars removeObject:caterpillar];
[self createSproutAtPostion:segment.position];
return;
}
// 2
[self.spritesBatchNode removeChild:segment.sprite cleanup:NO];
// 3
[self createSproutAtPostion:segment.position];
// 4
NSInteger indexOfSegement = [caterpillar.segments indexOfObject:segment];
NSMutableArray *headSegments = [NSMutableArray array];
NSMutableArray *tailsSegments = [NSMutableArray array];
// 5
[caterpillar.segments enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if(idx < indexOfSegement) {
[headSegments addObject:obj];
} else if(idx > indexOfSegement) {
[tailsSegments addObject:obj];
}
}
];
// 6
if([tailsSegments count] > 0) {
// 7
[tailsSegments reverse];
// 8
Caterpillar *newCaterpillar = [[[Caterpillar alloc] initWithGameLayer:self segments:tailsSegments level:self.level] autorelease];
newCaterpillar.position = [[tailsSegments objectAtIndex:0] position];
// 9
if(caterpillar.currentState == CSRight ||
caterpillar.previousState == CSRight) {
// Was heading right
if(caterpillar.currentState == CSDownLeft ||
caterpillar.currentState == CSDownRight) {
// Is heading down
newCaterpillar.previousState = CSUpRight;
} else {
// Is heading up
newCaterpillar.previousState = CSDownRight;
}
newCaterpillar.currentState = CSLeft;
} else {
// Was heading left
if(caterpillar.currentState == CSDownLeft ||
caterpillar.currentState == CSDownRight) {
// Is heading down
newCaterpillar.previousState = CSUpRight;
} else {
// Is heading up
newCaterpillar.previousState = CSDownRight;
}
newCaterpillar.currentState = CSRight;
}
[self.caterpillars addObject:newCaterpillar];
}
// 10
if([headSegments count] > 0) {
caterpillar.segments = headSegments;
} else {
[self.caterpillars removeObject:caterpillar];
}
}
|
- Если мы ударяем гусеницу из одного сегмента (только голову), мы удаляем эту гусеницу из игры и превращаем ее в росток.
- Удалите спрайт сегмента, который был поражен от узла пакета.
- Преобразуйте сегмент попадания в росток (мы реализуем этот метод на мгновение).
- Нам нужно разделить гусеницу на два массива: головной и хвостовой.
- Определите, где другие сегменты падают (голова или хвостовая часть) и добавьте их в соответствующие массивы.
- Проверьте, есть ли какие-либо хвостовые сегменты.
- Переверните массив хвостовых сегментов. Это категория на NSMutableArray, которая была упомянута выше. Нам нужно повернуть сегменты, чтобы переместить гусеницу в противоположном направлении.
- Создайте новый объект гусеницы, используя хвостовую часть. Мы реализуем этот метод на мгновение.
- Это смелость этого метода. Здесь мы определяем текущее направление (влево или вправо) и текущее общее направление (вверх или вниз) пораженной гусеницы, чтобы отправить новую гусеницу в противоположном направлении.
- Если осталась еще голова, мы просто устанавливаем гусеницу, в которую попали сегменты, на оставшиеся сегменты головы.
Вау, это было много, чтобы принять. Ты все еще со мной? Есть несколько методов, которые мы использовали здесь и в предыдущем коде, которые еще нужно реализовать. Первый метод для реализации — это createSproutAtPosition:
Добавьте следующую декларацию метода в свой приватный интерфейс вверху GameLayer.m:
1
|
— (void)createSproutAtPostion:(CGPoint)position;
|
Теперь реализуем следующий метод:
01
02
03
04
05
06
07
08
09
10
11
|
— (void)createSproutAtPostion:(CGPoint)position {
// 1
int x = (position.x — kGameAreaStartX) / kGridCellSize;
int y = (kGameAreaStartY — kGridCellSize + kGameAreaHeight + kGridCellSize/2 — position.y) / kGridCellSize;
// 2
Sprout *sprout = [[Sprout alloc] initWithGameLayer:self];
sprout.position = position;
[self.sprouts addObject:sprout];
_locations[x][y] = YES;
}
|
- Это переводит нашу позицию на экране координаты в координаты сетки. Используя наши навыки алгебры 8-го класса, мы получаем это из кода позиционирования, написанного во второй части .
- Создайте новый росток и добавьте его в игру.
Последний метод, который нам нужно реализовать, чтобы все это работало, это initWithGameLayer:segments:level:
в классе гусеницы. Этот метод будет отвечать за построение новой гусеницы из переданных сегментов. Откройте Caterpillar.h и добавьте следующее объявление метода:
1
|
— (id)initWithGameLayer:(GameLayer *)layer segments:(NSMutableArray *)segments level:(NSInteger)level;
|
Теперь откройте 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
|
— (id)initWithGameLayer:(GameLayer *)layer segments:(NSMutableArray *)segments level:(NSInteger)level {
if(self = [super initWithGameLayer:layer]) {
self.segments = segments;
self.level = level;
self.currentState = CSRight;
self.previousState = CSDownLeft;
// set the position of the rest of the segments
__block int x = 0;
__block Segment *parentSegment = [self.segments objectAtIndex:0];
parentSegment.parent = nil;
[self.segments enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Segment *segment = (Segment *)obj;
if(x++ > 0) {
if(![segment isEqual:parentSegment]) {
segment.parent = parentSegment;
}
parentSegment = segment;
}
}];
}
return self;
}
|
Этот метод практически идентичен нашему предыдущему initWithGameLayer:level:position:
method. Единственное отличие состоит не в том, чтобы выделять массив сегментов, а в том, что он устанавливает массив сегментов для входящих сегментов, которые передаются.
Продолжайте и запустите игру на этом этапе. Вы должны быть в состоянии полностью убить гусеницу, которая находится в игре.
Шаг 3: Столкновение Гусеницы Игрока
Последнее, что нам нужно, чтобы завершить жизненный цикл обнаружения столкновений, это реализовать столкновения между гусеницей и игроком. Если какая-либо часть гусеницы попадает в игрока, мы хотим уменьшить количество жизней игрока. Кроме того, игрок ненадолго станет непобедимым, так что гусеница не просто пронзит его.
Начните с открытия GameConfig.h и добавления следующей опции:
1
|
#define kPlayerInvincibleTime 15
|
Теперь откройте Caterpillar.m, импортируйте Player.h и добавьте следующий код в конец update:
метод непосредственно перед тем, как установить положение гусеницы:
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
|
static int playerInvincibleCount = kPlayerInvincibleTime;
static BOOL playerHit;
CGRect playerRect = [self.gameLayer.player getBounds];
// 1
[self.segments enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Segment *segment = (Segment *)obj;
CGRect segmentRect = [segment getBounds];
if(CGRectIntersectsRect(segmentRect, playerRect) && playerInvincibleCount == kPlayerInvincibleTime) {
*stop = YES;
playerHit = YES;
// 2
self.gameLayer.player.lives—;
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationPlayerLives object:nil];
}
}];
// 3
if(playerHit) {
if(playerInvincibleCount > 0) {
playerInvincibleCount—;
} else {
playerHit = NO;
playerInvincibleCount = kPlayerInvincibleTime;
}
}
|
- Перечислите каждый из сегментов, чтобы определить, сталкиваются ли они с игроком.
- Если игрок получил удар, уменьшите его жизнь и отправьте уведомление. Это должно автоматически отражаться в пользовательском интерфейсе на основе кода, который вы написали в части 3.
- Если игрок был поражен, проверьте, были ли они все еще в непобедимом периоде. Если нет, сбросьте непобедимый счетчик, чтобы они могли получить удар снова.
Теперь запустите игру и позвольте гусенице ударить вас. Это должно удалить одну из ваших жизней из верхней части экрана.
Вывод
Обнаружение столкновения никогда не было легкой задачей, и мы только поцарапали поверхность. Если вы хотите углубиться в обнаружение столкновений, взгляните на использование Box2D с Cocos2D.
В следующий раз
В следующем и последнем уроке серии мы обсудим игровой процесс. Это будет включать в себя условия победы, очки, звуки, игру и высокий балл.
Удачного кодирования!