Статьи

Создайте игру Smashing Monster с Cocos2D: движение и анимация

Из этого туториала вы узнаете, как использовать iOS-инфраструктуру Cocos2D для создания простых, но продвинутых 2D-игр, предназначенных для всех устройств iOS. Он предназначен как для начинающих, так и для опытных пользователей. Попутно вы узнаете об основных понятиях Cocos2D, сенсорном взаимодействии, меню и переходах, действиях, частицах и столкновениях. Читай дальше!

Этот урок — вторая запись в серии из трех частей Monster Smashing. Убедитесь, что вы закончили предыдущий раздел, прежде чем начать.


Организация серии:

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


Монстры являются основными объектами игры. У каждого монстра есть несколько уникальных атрибутов, таких как спрайты, движение, скорость и действие убийства. Для представления объекта мы можем использовать определенный класс, поэтому мы создали класс Monster для представления монстра.

Обычно в Objective-C мы создаем новый подкласс NSObject . В этом случае нам нужно использовать класс CCNode , который включает в cocos2d.h заголовки Foundation.h и cocos2d.h . Чтобы создать новый класс в XCode, выберите File -> New -> New File …. В левой таблице появившейся панели выберите Cocos2D из раздела iOS. Выберите класс CCNode на верхней панели и нажмите « Далее» . Мы назовем класс «Монстр».

Рисунок 1: Класс CCNode
Иллюстрация выбора шаблона (Xcode).

Теперь нам нужно объявить переменные экземпляра. В Monster.h добавьте пять переменных экземпляра:

  • NSString *monsterSprite
  • NSString *splashSprite
  • int movement
  • float minVelocity
  • float maxVelocity
  • int killMethod

Теперь, как и в случае любого ориентированного объектного языка, нам нужно реализовать методы установки и получения. В target-C свойства являются удобной альтернативой написанию методов доступа для переменных экземпляра. Это экономит много времени на ввод текста и облегчает чтение ваших классов. Добавьте вышеупомянутые свойства в файл .h.

1
2
3
4
5
6
@property(nonatomic,readwrite,retain) NSString *monsterSprite;
@property(nonatomic,readwrite,retain) NSString *splashSprite;
@property(nonatomic, readwrite) int movement;
@property(nonatomic, readwrite) float minVelocity;
@property(nonatomic, readwrite) float maxVelocity;
@property(nonatomic, readwrite) int killMethod;

Прежде чем продолжить, вам нужно инициализировать несколько вещей. мы можем добавить фон к нашей игре, как мы это делали в первом разделе учебника. Мы можем использовать тот же самый в сцене меню. Если вы не помните, как это сделать, вы можете использовать следующий код для метода -(id) init (MonsterRun.m).

1
2
3
4
5
6
7
self.isTouchEnabled = YES;
 
// Add background
        winSize = [CCDirector sharedDirector].winSize;
        CCSprite *background = [CCSprite spriteWithFile:@»WoodRetroApple_iPad_HomeScreen.jpg»];
        background.position = ccp(winSize.width/2, winSize.height/2);
        [self addChild:background z:-2];

Теперь нам нужно инициализировать несколько «монстров». Прежде чем вы начнете добавлять монстров, нам нужно создать NSMutableArray для их хранения. Еще раз, изображения для спрайтов монстров доступны в папке ресурсов.

Мы также хотели бы поблагодарить Родриго Беллао за предоставленные нам изображения для монстров.

В MonsterRun.m добавьте два объекта NSMutableArray : _monsters и _monstersOnScreen . Затем в методе init мы должны инициализировать эти массивы, чтобы использовать их. Следующий фрагмент может помочь нам достичь этого.

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
//initializing monsters
_monsters = [[NSMutableArray alloc] init];
_monstersOnScreen = [[NSMutableArray alloc] init];
 
Monster *m1 = [[Monster alloc] init];
[m1 setTag:1];
[m1 setMonsterSprite:[[NSString alloc] initWithString:@»monsterGreen.png»]];
[m1 setSplashSprite:[[NSString alloc] initWithString:@»splashMonsterGreen.png»]];
[m1 setMinVelocity:2.0];
[m1 setMaxVelocity:8.0];
[m1 setMovement:1];
[m1 setKillMethod:1];
[_monsters addObject:m1];
 
Monster *m2 = [[Monster alloc] init];
[m2 setTag:2];
[m2 setMonsterSprite:[[NSString alloc] initWithString:@»monsterBlue.png»]];
[m2 setSplashSprite:[[NSString alloc] initWithString:@»splashMonsterBlue.png»]];
[m2 setMinVelocity:2.0];
[m2 setMaxVelocity:8.0];
[m2 setKillMethod:2];
[m2 setMovement:1];
[_monsters addObject:m2];
 
Monster *m3 = [[Monster alloc] init];
[m3 setTag:3];
[m3 setMonsterSprite:[[NSString alloc] initWithString:@»monsterRed.png»]];
[m3 setSplashSprite:[[NSString alloc] initWithString:@»splashMonsterRed.png»]];
[m3 setMinVelocity:3.0];
[m3 setMaxVelocity:6.0];
[m3 setKillMethod:1];
[m3 setMovement:2];
[_monsters addObject:m3];

Эти массивы должны использоваться для хранения всех монстров для данного уровня.


Теперь, когда у нас есть несколько монстров в памяти, следующий шаг — вывести их на экран. Для этого нам просто нужно несколько строк.

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

1
[self schedule:@selector(addMonster:) interval:1.0];

Селектор указывает метод, который будет называться addMonster . На данный момент у нас нет этого метода, но он скоро будет создан. Планировщик также может получать интервал времени, который представляет время обновления конкретного спрайта.

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

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
— (void) addMonster:(ccTime)dt {
     
    //select a random monster from the _monsters Array
    int selectedMonster = arc4random() % [_monsters count];
     
    //get some monster caracteristics
    Monster *monster = [_monsters objectAtIndex:selectedMonster];
    int m = [monster movement];
     
    //!IMPORTANT — Every Sprite in Screen must be an new CCSprite!
    CCSprite *spriteMonster = [[CCSprite alloc] initWithFile:[monster monsterSprite]];
    spriteMonster.tag = [monster tag];
     
     
    //BLOCK 1 — Determine where to spawn the monster along the Y axis
    CGSize winSize = [CCDirector sharedDirector].winSize;
    int minX = spriteMonster.contentSize.width / 2;
    int maxX = winSize.width — spriteMonster.contentSize.width/2;
    int rangeX = maxX — minX;
    int actualY = (arc4random() % rangeX) + minX;
     
    //BLOCK 2 — Determine speed of the monster
    int minDuration = [monster minVelocity];
    int maxDuration = [monster maxVelocity];
    int rangeDuration = maxDuration — minDuration;
    int actualDuration = (arc4random() % rangeDuration) + minDuration;
     
    if(m == 1){ //STRAIGHT MOVIMENT
         
        //BLOCK 3 — Create the monster slightly off-screen along the right edge,
        // and along a random position along the Y axis as calculated above
        spriteMonster.position = ccp( actualY,winSize.height + spriteMonster.contentSize.height/2);
        [self addChild:spriteMonster];
         
        //BLOCK 4 — Create the actions
        CCMoveTo * actionMove = [CCMoveTo actionWithDuration:actualDuration position:ccp( actualY,-spriteMonster.contentSize.height/2)];
        CCCallBlockN * actionMoveDone = [CCCallBlockN actionWithBlock:^(CCNode *node) {
            [_monstersOnScreen removeObject:node];
            [node removeFromParentAndCleanup:YES];
        }];
         
        [spriteMonster runAction:[CCSequence actions:actionMove, actionMoveDone, nil]];
 
        [_monstersOnScreen addObject:spriteMonster];
    }
    else if(m == 2){ //ZIGZAG-SNAKE MOVIMENT
         
        /* Create the monster slightly off-screen along the right edge,
         and along a random position along the Y axis as calculated above
         */
        spriteMonster.position = ccp( actualY,winSize.height + spriteMonster.contentSize.height/2);
        [self addChild:spriteMonster];
                 
        CCCallBlockN * actionMoveDone = [CCCallBlockN actionWithBlock:^(CCNode *node) {
            [_monstersOnScreen removeObject:node];
            [node removeFromParentAndCleanup:YES];
             
        }];
         
        // ZigZag movement Start
        NSMutableArray *arrayBezier = [[NSMutableArray alloc] init];
        ccBezierConfig bezier;
        id bezierAction1;
        float splitDuration = actualDuration / 6.0;
        for(int i = 0; i< 6; i++){
             
            if(i % 2 == 0){
                bezier.controlPoint_1 = ccp(actualY+100,winSize.height-(100+(i*200)));
                bezier.controlPoint_2 = ccp(actualY+100,winSize.height-(100+(i*200)));
                bezier.endPosition = ccp(actualY,winSize.height-(200+(i*200)));
                bezierAction1 = [CCBezierTo actionWithDuration:splitDuration bezier:bezier];
            }
            else{
                bezier.controlPoint_1 = ccp(actualY-100,winSize.height-(100+(i*200)));
                bezier.controlPoint_2 = ccp(actualY-100,winSize.height-(100+(i*200)));
                bezier.endPosition = ccp(actualY,winSize.height-(200+(i*200)));
                bezierAction1 = [CCBezierTo actionWithDuration:splitDuration bezier:bezier];
            }
             
            [arrayBezier addObject:bezierAction1];
        }
         
        [arrayBezier addObject:actionMoveDone];
         
        id seq = [CCSequence actionsWithArray:arrayBezier];
         
        [spriteMonster runAction:seq];
        // ZigZag movement End
 
        [_monstersOnScreen addObject:spriteMonster];
    }
}

Возможно, вы заметили, что в коде есть комментарии. Комментарии помогут нам (как программистам, так и читателям) реализовать и изучить каждую инструкцию. В этом уроке монстры выбираются случайным образом (однако, в третьей части мы покажем, как использовать pList для вывода монстров на экран).

Первая строка нашего кода делает именно это. Мы получаем общее количество монстров в нашем массиве, а затем случайным образом выбираем, кого из них мы разместим на экране. После того, как монстр выбран, мы определяем некоторые свойства, такие как движение, скорость, диапазон по оси XX и его тег.

Мы определили три блока, которые определяют конкретные свойства.

  • Блок 1 определяет, где на экране мы будем охватывать монстров. Обратите внимание, что монстр будет появляться только в видимой области экрана. Для этого мы получаем диапазон оси XX и затем случайным образом размещаем монстра.
  • Блок 2 определяет скорость монстра. Обратите внимание, что для каждого типа монстров будет определена минимальная и максимальная скорость. Скорость монстра — это длительность в секундах, через которую монстр будет проходить через экран. Эта длительность будет между minDuration и maxDuration, определенной для объекта-монстра в методе init
  • Блок 3 создает монстра немного за кадром вдоль правого края и вдоль случайной позиции вдоль оси XX, рассчитанной выше
  • Блок 4 создает действия для прямого движения. CCMoveTo * actionMove два действия, само движение CCMoveTo * actionMove и одно, которое вызывается, когда спрайт покидает экран ( CCCallBlockN * actionMoveDone ). Чтобы создать действие с прямым движением, мы используем объект CCMoveTo , который получает продолжительность действия и конечную позицию. Мы также используем CCCallBlockN . Этот конкретный объект будет обнаруживать, когда монстр сходит с экрана. Использование CCCallBlockN выгодно, потому что Cocos2D уже имеет некоторые механизмы для определения, покидает ли конкретный ресурс экран.

Мы определили два движения: прямое и зигзагообразное. Прямой может быть проанализирован в блоке 4, упомянутом выше. Зигзаг использует путь Безье для создания эффекта зигзага и будет показан ниже.


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

Созданные кривые всегда будут перемещаться на 100 пикселей влево или вправо, и в то же время объект будет двигаться на 200 пикселей ниже. Цикл for создаст путь монстра. Поскольку экран с нормальным разрешением iPad имеет 1024 пикселя в вертикальном положении, нам нужно выполнить цикл шесть раз (200px * 6 = 1200px). Условие внутри цикла for будет определять, является ли итерация нечетной или парной. Когда число нечетное, монстр перемещается влево. Если число — пара, монстр двигается вправо.

Теперь каждое условие будет вычислять controlPoint1 и controlPoint2 , которые используются для управления и направления кривой на экране. endPosition , как следует из названия, является конечной позицией, в которой каждый монстр окажется после каждой кривой. Мы объединяем все эти действия в массив и создаем последовательность из него. Как и в случае с прямым движением, мы запустим действие и добавим монстра в массив.

Пришло время построить и запустить проект. Ваша игра должна выглядеть примерно так, как показано ниже.

Рисунок 2: Монстр на экране
Иллюстрация игры с несколькими монстрами.

Игра с монстрами, которых невозможно убить, не является игрой, верно?

Пришло время добавить некоторое взаимодействие между экраном и пользователем. Мы будем следовать той же философии, которую мы использовали выше, код и объяснение.

Cocos2D предоставляет нам несколько механизмов для сенсорного взаимодействия. Сейчас мы сосредоточимся только на одном прикосновении. Для этого нам нужно добавить специальный метод ccTouchesBegan . Метод получает событие и будет действовать в соответствии с этим событием. Очевидно, событие является сенсорным событием. Фрагмент представлен ниже.

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
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
     
    NSMutableArray *monstersToDelete = [[NSMutableArray alloc] init];
     
    UITouch * touch = [[touches allObjects] objectAtIndex:0];
    CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
    for (CCSprite *monster in _monstersOnScreen) {
        if (CGRectContainsPoint(monster.boundingBox, touchLocation)) {
            [monstersToDelete addObject:monster];
             
            //add animation with fade — splash
            Monster *m = [_monsters objectAtIndex:(monster.tag-1)];
            CCSprite *splashPool = [[CCSprite alloc] initWithFile:[m splashSprite]];
             
            if([m killMethod] == 1){
                splashPool.position = monster.position;
                [self addChild:splashPool];
                 
                CCFadeOut *fade = [CCFadeOut actionWithDuration:3];
                CCCallFuncN *remove = [CCCallFuncN actionWithTarget:self selector:@selector(removeSprite:)];
                CCSequence *sequencia = [CCSequence actions: fade, remove, nil];
                [splashPool runAction:sequencia];
                //finish splash
            }
            if([m killMethod] == 2){
                // in Part 3 — Particles section
            }
            break;
             
        }
    }
     
     for (CCSprite *monster in monstersToDelete) {
         [monster stopAllActions];
         [_monstersOnScreen removeObject:monster];
         [self removeChild:monster cleanup:YES];

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

Следующий шаг — узнать местоположение сенсорного взаимодействия. С этим местоположением мы зациклим все спрайты на экране и проверим, находится ли местоположение касания внутри невидимой рамки, содержащей монстра. Мы будем использовать f (CGRectContainsPoint(monster.boundingBox, touchLocation)) . Если это правда, мы добавим монстров во вспомогательный массив. Плюс, мы добавим две анимации всплеска, когда монстр убит. Первый — простой всплеск, а второй — частица. Мы обсудим это в третьей части. Чтобы создать заставку, мы проверяем тип монстра и затем добавляем этого монстра в пул заставки. CCFadeOut создаст эффект затухания на заставке, а CCCallFuncN вызовет другой метод для удаления спрайта Monster.

Теперь нам просто нужна маленькая вещь. При CCCallFuncN *remove метод вызова removeSprite необходимо создать, и целью является удаление этого спрайта. Добавьте этот метод, используя следующий код.

1
2
3
-(void) removeSprite:(id)sender {
    [self removeChild:sender cleanup:YES];
}

Наконец, когда цикл завершен, взаимодействие с for (CCSprite *monster in monstersToDelete) проверяется на всех объектах сцены, которые мы имеем в другом цикле, for (CCSprite *monster in monstersToDelete) . Он будет перебирать monstersToDelete и удалять объект.

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

Рисунок 1: Монстр на экране с всплеск анимации
Иллюстрация игры с несколькими монстрами с всплеск анимации.

На этом этапе вы должны понимать и выполнять следующие задачи:

  • Добавьте спрайты на экран.
  • Определите свойства спрайта.
  • Определите пользовательские классы.
  • Инициализируйте игровое поле.
  • Определите пользовательские движения и действия.
  • Знать, как использовать сенсорное взаимодействие.

В следующем уроке мы узнаем о звуке и игровой механике!