Статьи

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

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


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

В предыдущем уроке я показал вам, как создавать объекты-гусеницы и сегменты. Вы узнали, как двигать головку гусеницы, и чтобы каждый из сегментов следовал за ней.

Сегодня мы будем обсуждать базовый ИИ гусеницы. Я покажу вам, как гусеница может взаимодействовать с окружающей средой и реагировать на препятствия, такие как стены и ростки. К концу этого урока ваша гусеница сможет полностью ориентироваться в любой среде, созданной в части 2.


В последнем уроке мы добавили простой код для продвижения гусеницы. Это было просто временно и больше не нужно. Итак, откройте Caterpillar.m и удалите следующую строку из update: метод:

1
x += kGridCellSize;

Скоро мы изменим переменные x и y.


Оказывается, есть блок кода, который повторно используется каждый раз, когда происходит коллизия. Столкновение — это всякий раз, когда головной узел гусеницы пересекает правую стену, левую стенку, верхнюю стенку, нижнюю стенку, росток или игрока. Цель метода столкновения — соответственно отрегулировать новое состояние гусениц.

Прежде чем я покажу вам метод столкновения, давайте обсудим правила гусеницы, основанные на оригинальной игре.

  1. Гусеница движется в своем текущем направлении, пока не появится препятствие.
  2. Если гусеница движется вниз и сталкивается со стеной или ростком, ее следующие движения — один пробел вниз и один пробел в противоположном направлении.
  3. Если гусеница движется вверх и сталкивается со стеной или ростком, ее следующие движения — это одно пространство вверх и одно пространство в противоположном направлении.
  4. Если гусеница сталкивается с нижним рядом, ее общее направление изменяется на
  5. Если гусеница сталкивается с верхним рядом, ее общее направление меняется на нисходящее.

Учитывая эти правила, давайте реализуем метод collision: . Откройте файл Caterpillar.m и добавьте следующую декларацию метода в интерфейс частного класса, а также добавьте реализацию:

1
2
3
@interface Caterpillar(Private)
    — (void) collision;
@end

И реализация …

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
— (void) collision {
    // 1
    BOOL down = self.currentState == CSDownLeft ||
                self.currentState == CSDownRight ||
                self.previousState == CSDownLeft ||
                self.previousState == CSDownRight;
    // 2
    self.previousState = self.currentState;
 
    // 3
    if(down) {
        if(self.currentState == CSRight) {
            self.currentState = CSDownLeft;
        } else {
            self.currentState = CSDownRight;
        }
     } else {
         if(self.currentState == CSRight) {
                self.currentState = CSUpLeft;
            } else {
                self.currentState = CSUpRight;
            }
     }
}
  1. Первым шагом здесь является определение направления движения гусеницы в целом (вверх или вниз). Мы можем определить это, если текущее или предыдущее состояние гусеницы является одним из состояний «вниз».
  2. Следующий шаг — установить предыдущее состояние в текущее состояние. Это будет полезно, когда мы начнем перемещать гусеницу в соответствии с ее состояниями.
  3. Наконец, мы определяем, должны ли мы быть в верхнем левом / правом состоянии или нижнем левом / правом состоянии, и устанавливаем его.

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


Поскольку мы в основном реализовали наш Caterpillar как один большой конечный автомат, эта часть в конечном итоге станет одним большим оператором переключения. Случаи заявления будут каждое из возможных состояний гусеницы. В приведенном ниже коде мы начнем с наиболее распространенных состояний: CSRight и CSLeft . Добавьте следующий код в метод update: внутри вашего класса Caterpillar.m. Убедитесь, что вы добавили его перед строкой self.position = ccp(x,y); ,

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
switch (self.currentState) {
    case CSRight:
        // 1
        if(x + kGridCellSize >= kGameAreaStartX + kGameAreaWidth) {
 
            // 2
            if(y — kGridCellSize <= kGameAreaStartY) {
                self.previousState = CSUpLeft;
                self.currentState = CSRight;
            } else if(y >= kGameAreaStartY + kGameAreaHeight — kGridCellSize) {
                // 3
                self.previousState = CSDownRight;
                self.currentState = CSRight;
            } else {
                // 4
                if(self.previousState == CSDownRight ||
                   self.previousState == CSDownLeft) {
                   self.currentState = CSDownLeft;
                } else {
                    self.currentState = CSUpLeft;
                }
                self.previousState = CSRight;
                [self update:4.0];
                return;
            }
            // 5
            [self collision];
        } else {
        // 6
            x = x + kGridCellSize;
        }
 
        break;
    case CSLeft:
 
        // Check for a wall collision
        if(x <= kGameAreaStartX) {
 
            if(y — kGridCellSize <= kGameAreaStartY) {
                self.previousState = CSUpRight;
                self.currentState = CSLeft;
            } else if(y >= kGameAreaStartY + kGameAreaHeight) {
                // Top collision
                self.previousState = CSDownLeft;
                self.currentState = CSLeft;
            } else {
                // Left wall collision
                if(self.previousState == CSDownRight ||
                   self.previousState == CSDownLeft) {
                    self.currentState = CSDownRight;
                } else {
                    self.currentState = CSUpRight;
                }
                self.previousState = CSLeft;
                [self update:4.0];
                return;
            }
 
            [self collision];
        } else {
            x = x — kGridCellSize;
        }
 
        break;
}

Поскольку эти случаи симметричны, я только собираюсь объяснить CSRight.

  1. Сначала мы проверим, столкнулась ли гусеница с правым краем. Если это так, нам нужно увидеть, где он находится, чтобы определить, что делать дальше.
  2. Эта первая проверка определяет, находимся ли мы в нижней части экрана, если это так, мы просто движемся в другом направлении и устанавливаем предыдущую позицию в направлении «вверх», чтобы отправить гусеницу назад.
  3. Это противоположно # 2 и проверяет, находится ли гусеница в верхней части экрана.
  4. Это базовое состояние этого дела. Он срабатывает, когда вы попадаете в боковую стенку и не находитесь ни сверху, ни снизу. Если это так, мы устанавливаем предыдущее состояние и снова вызываем этот метод. Причина, по которой мы снова вызываем метод, заключается в том, что мы не хотим продолжать движение гусеницы в текущем направлении (в противном случае он будет проходить один сегмент слишком далеко через стену).
  5. Вызовите метод столкновения, чтобы настроить наши следующие / предыдущие позиции.
  6. Наконец, если столкновения нет, переместите гусеницу вперед.

Если вы запустите код на этом этапе, гусеница будет двигаться вперед, пока не достигнет правой стены. Это остановится, и остальная часть тела будет следовать. Давайте реализуем остальные состояния, чтобы реагировать на другие изменения состояния.

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
// …
 
case CSDownLeft:
    // 1
    if(self.moveCount == 0) {
        y = y — kGridCellSize;
        self.moveCount++;
    } else {
    // 2
        x = x — kGridCellSize;
        self.moveCount = 0;
        self.currentState = CSLeft;
        self.previousState = CSDownLeft;
    }
 
    break;
case CSDownRight:
    if(self.moveCount == 0) {
        y = y — kGridCellSize;
        self.moveCount++;
    } else {
        x = x + kGridCellSize;
        self.moveCount = 0;
        self.currentState = CSRight;
        self.previousState = CSDownRight;
    }
 
    break;
case CSUpRight:
    if(self.moveCount == 0) {
        y = y + kGridCellSize;
        self.moveCount++;
    } else {
        x = x + kGridCellSize;
        self.moveCount = 0;
        self.currentState = CSRight;
        self.previousState = CSUpRight;
    }
 
    break;
case CSUpLeft:
 
    if(self.moveCount == 0) {
        y = y + kGridCellSize;
        self.moveCount++;
    } else {
        x = x — kGridCellSize;
        self.moveCount = 0;
        self.currentState = CSLeft;
        self.previousState = CSUpLeft;
    }
 
    break;
default:
    break;

Как и в предыдущем разделе, я расскажу вам только об одном из этих случаев, поскольку все они похожи.

  1. Все эти состояния требуют 2 шага для работы. Первый шаг — спуск, следующий — изменение направления. Таким moveCount свойство moveCount будет иметь значение 0 или 1. Значение 0 приведет к moveCount состояния, а значение 1 — к смещению вперед.
  2. На этом этапе мы сбрасываем влево или вправо, а затем сбрасываем свойство moveCount . В основном, продолжаем как обычно.

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


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

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
// 1
CGRect caterpillarBounds = [self getBounds];
 
// 2
[self.gameLayer.sprouts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    Sprout *sprout = (Sprout *)obj;
    CGRect sproutBounds = [sprout getBounds];
 
    // 3
    if(self.currentState == CSRight) {
        CGRect rightBounds = caterpillarBounds;
        rightBounds.origin.x = rightBounds.origin.x + kGridCellSize;
        if(CGRectIntersectsRect(rightBounds, sproutBounds)) {
            [self collision];
            *stop = YES;
        }
    }
 
    // 4
    if(self.currentState == CSLeft) {
        CGRect leftBounds = caterpillarBounds;
        leftBounds.origin.x = leftBounds.origin.x — kGridCellSize;
        if(CGRectIntersectsRect(leftBounds, sproutBounds)) {
            [self collision];
            *stop = YES;
        }
    }
 
}];
  1. Мы начнем с получения границ гусеницы. В этом случае наша позиция гусеницы основана на сегменте головы, поэтому этот метод просто возвращает границы сегмента головы.
  2. При каждом update: вызове гусеницы мы перечисляем все объекты прорастания, чтобы проверить, не было ли столкновения.
  3. Проверьте, не столкнулась ли гусеница с ростком справа.
  4. Проверьте, не столкнулась ли гусеница с ростком слева.

Если произошла коллизия, мы вызываем метод коллизии, чтобы обновить состояние, и устанавливаем для переменной *stop значение true, чтобы прекратить перечисление.

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

Caterpillar

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

Удачного кодирования!