SpriteKit — Продолжаем писать игру Happy Airjet — Часть 10

Продолжим писать лайтовую версию Happy Airjet. В двух предыдущих частях в проект добавили фон с небольшой анимацией, летящие рандомно облака и Score. Сейчас добавим самолет, обработаем тап по экрану и займемся физикой — чтобы самолет падал вниз и сталкивался с облаками.
Добавляем спрайт самолета. Сначала в Scene.h вставим:

SKSpriteNode *plane;

В Scene.m у нас есть пустой метод createPlane. Начнем заполнять его. Сначала просто иницилизируем спрайт plane:

plane = [SKSpriteNode spriteNodeWithImageNamed:@"airjet"];
plane.position = CGPointMake(50, self.size.height/2);
plane.name = @"PLANE";
[self addChild:plane];

Тут все просто и понятно. Запускайте и проверяйте. Должно быть так:
scene4
Теперь добавим SKPhysicsBody самолету. После этого самолет начнет подчиняться некоторым обычным законам физики. Например сила притяжения, т.е. будет падать вниз. Добавьте в Scene.h:

SKPhysicsBody *planeBody;

а в Scene.m в метод createPlane допишем:

/*
Иницилизируем SKPhysicsBody и устанавливаем размеры "физического тела" для самолета. Оно может отличаться от реального размера спрайта.
Но у нас оно такое же как сам спрайт.
*/
planeBody=[SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(plane.size.width, plane.size.height)];
[planeBody setDynamic:NO]; //Выключаем физику. если NO-физика игнорируется. Чтобы самолет при старте сразу не падал.
[plane setPhysicsBody:planeBody]; //Добавляем SKPhysicsBody к самолету

Запускайте и проверяйте. Чтобы проверить работоспособность физики, поставьте [planeBody setDynamic:YES] и посмотрите, при запуске самолет сразу должен падать вниз. Теперь обработаем тап по экрану. Для этого в Scene.m добавим метод touchesBegan, он выполняется при начале касания экрана:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    if (!planeBody.dynamic){ //проверяем включена ли физика у самолета. При старте игры она у нас выключена [planeBody setDynamic:NO].
        plane.position = CGPointMake(50, self.size.height/2); //ставим стартовые координаты, это чтобы после гейм овера на середину самолет вставал
        [planeBody setDynamic:YES]; //если выключена, то включаем
    }
    
    if ((self.size.height-20)>plane.position.y)  { //проверяем, если не вылетел за пределы экрана сверху, то
        [plane.physicsBody setVelocity:CGVectorMake(0, 350)]; //если ок, придаем самолету скорости вверх, чтобы он приподнялся
    }
}

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

-(void)update:(CFTimeInterval)currentTime 
//это спрайткитовый протокол который выполняется при обновлении картинки на экране, в первых главах писал об этом
{
plane.zRotation = M_PI * plane.physicsBody.velocity.dy * 0.0005; //даем градус при взлете и падении самолету
    
    if (plane.position.y<100) { //проверяем если самолет упал в море
        [planeBody setDynamic:NO]; //выключаем ему физику, чтобы он в бездну не улетел
    }
}

Запускайте, тапайте и проверяйте. Самолет должен быть не горизонтален. Теперь надо для облаков добавить SKPhysicsBody для того, чтобы отслеживать коллизии (столкновения). В Scene.m в createCloud, пред строкой [self addChild:cloud]; добавляем:

    SKPhysicsBody *body=[SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(cloud.size.width-(cloud.size.width/3), cloud.size.height-(cloud.size.height/3))];
    [body setUsesPreciseCollisionDetection:YES];
    [body setDynamic:NO];
    [cloud setPhysicsBody:body];

и для второго облака добавляем перед строкой [self addChild:cloud2];

        SKPhysicsBody *body2=[SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(cloud2.size.width-(cloud2.size.width/3), cloud2.size.height-(cloud2.size.height/3))];
        [body2 setUsesPreciseCollisionDetection:YES];
        [body2 setDynamic:NO];
        [cloud2 setPhysicsBody:body2];

Запускайте и посмотрите как теперь самолет взаимодействует с облаками, потому что теперь у облаков физика есть. Если самолет упадет, игра вылетит. В самом вверху, перед #import пропишите:

static const uint32_t planeCategory=  0x1 << 1;

эта статичная категория-маска. Смысл ее потом объясню в статье про Физику. Теперь самолету дадим маску, в createPlane допишем:

        planeBody.categoryBitMask = planeCategory;
        planeBody.contactTestBitMask =planeCategory;

Далее в didMoveToView добавьте строку:

self.physicsWorld.contactDelegate = self;

Это мы "подписались" на протоколы о коллизиях. И теперь можем реализовать протокол didBeginContact, который выполняется при контакте:

- (void)didBeginContact:(SKPhysicsContact *)contact
{
    NSLog(@"BOOM");
}

Запустите и проверьте для начала, при столкновении в консоле должно появляться BOOM. Если нет, перепроверьте все. Вообщем теперь вы знаете место которое сработает при столкновении с облаком и можете вывести заставку гейм овер или списать жизнь и начать заново и пр. Включайте фантазию вообщем. Написав этот небольшой код, я ставил перед собой задачу наиболее наглядно показать простоту построения сцены. Поэтому если вы будете писать что нибудь, не забывайте про ООП)
Далее будет большая часть про SKPhysicsBody, в Happy Airjet начали смотреть физику, но это капля в море. На мероприятии WWDC2014 Apple представила обновленный SpriteKit с еще более крутой физикой, об этом тоже напишу. Удачи и пишите комменты с фидбеком

  • Tima

    Если я не ошибаюсь, в файле Scene.h надо изменить:
    @interface Scene : SKScene
    на:
    @interface Scene : SKScene

    • floMaster

      Все верно! Забыл протокол! Поправлю

  • Доброго времени суток! Отличные уроки, интересные.
    В процессе, у меня возник вопрос:
    Я решил немного модифицировать код, так чтобы облака взрывались, когда самолет налетает на них. Эффект взрыва работает, но взрывается не облако с которым сталкивается самолет, а последнее созданное облако.
    Я всячески пытался исправить это, но безрезультатно. Мне кажется, что я неверно обращаюсь к методу облака.
    Так я вызываю взрыв: [self NodeWithBang: cloudBody.node];
    Я добавил глобальную переменную SKPhysicsBody *cloudBody; и присвоил ей физические свойства SKSpriteNode* cloud;
    Но это не дало результата. NodeWithBang — метод с эффектом взрыва.

    • floMaster

      Спасибо. Тебе не надо делать глобальную переменную SKPhysicsBody *cloudBody. Для взрыва бери облако из — (void)didBeginContact:(SKPhysicsContact *)contact.

      SKSpriteNode *firstNode, *secondNode, *yo;
      firstNode = (SKSpriteNode *)contact.bodyA.node;

      secondNode = (SKSpriteNode *) contact.bodyB.node;

      if ([firstNode.name isEqualToString:@»CLOUD»]) {
      yo=firstNode;
      }else if ([secondNode.name isEqualToString:@»CLOUD»]){
      yo=secondNode;
      }

      [self NodeWithBang: yo];

      Т.е. во время контакта, ты берешь два спрайта которые столкнулись, выясняешь которой из них Облако, а который самолет, и взрываешь облако) Оно будет как раз то с которым было столкновение

      • Великолепно работает!

        СПАСИБО!!!! 🙂

        Нашел отличную статью на хабре по библиотеке SpriteKit:

        http://habrahabr.ru/post/225517/

        Возможно Вы заведете раздел типа «дайджест» где сможете размещать статьи с других сайтов?
        Ваш блог отличное пособие на русском языке, ждем продолжения. 🙂

        • floMaster

          С продолжением проблемы (