Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. iOS SDK
Code

Создаем игру Блекджек в Swift 3 и SpriteKit

by
Difficulty:IntermediateLength:LongLanguages:

Russian (Pусский) translation by Ellen Nelson (you can also view the original English article)

Final product image
What You'll Be Creating

В этом уроке мы будем создавать игру Блэкджек в SpriteKit используя Swift 3. Вы узнаете о взаимодействии с "касанием" (тач), создании визуальной анимации и многих других аспектах, которые будут полезны при создании игр в SpriteKit.

1. Создаём проект и импортируем ресурсы

Откройте Xcode и выберите Create a new Xcode project или выберите New > Project... в меню File. Убедитесь, что выбрано iOS и выберите шаблон Game.

new_project

Затем, укажите что угодно в Product Name, Organization Name, и Organization Identifier. Убедитесь, что Language установлен на Swift, Game Technology установлен на SpriteKit, и в Devices выбран iPad.

project_options

Выберите куда сохранить файлы проекта и нажмите Создать.

Импорт вспомогательных классов

Загрузите GitHub репозиторий для этого проекта. Там вы найдете папку с классами classes. Откройте эту папку и перетащите все файлы в папку вашего проекта, которую вы могли бы назвать blackjack. Убедитесь, что отмечена галочка "Скопировать при необходимости" — Copy items if needed, а так же указана целевая папка назначения (targets  blackjack).

File options with Copy items if needed box checked

Импортируем изображения

Также, как в этом уроке в репозитории GitHub вы найдёте папку под названием tutorial images. Внутри вашего проекта найдите и откройте Assets.xcassets, а затем переместите туда все изображения из папки. Xcode автоматически создаст атласы текстур из этих изображений.

Tutorial images folder in GitHub

2. Настраиваем наш проект

В навигаторе проекта есть два файла, которые вы можете удалить (Gamescene.sks и Actions.sks). Удалите эти два файла и выберите Move To Trash. Эти файлы используются встроенным редактором сцен Xcode, который можно использовать для визуальной компоновки ваших проектов. Тем не менее, мы будем создавать все с помощью кода, поэтому эти файлы не нужны.

Откройте GameViewController.swift, удалите его содержимое и замените его следующим.

Класс GameViewController наследуется от UIViewController и будет иметь SKView в качестве своего представления. Внутри метода viewDidLoad мы проходим до свойства view до экземпляра SKView, используя оператор as! и настраиваем представление.

Если бы вы запустили этот проект свежесозданным, вы могли бы заметить текст в правом нижнем углу экрана. Это то, для чего нужны свойства showsFPS и showsNodeCount, показывающие количество кадров в секунду при работе игры и количество SKNodes видимых в сцене. Нам не нужна эта информация, поэтому задаём здесь false.

Свойство ignoreSiblingOrder используется для определения порядка отрисовки SKNodeов внутри игры. Мы задали тут false, потому что нам нужен наш собственный SKNodes для отрисовки в том порядке, в котором они были добавлены в сцену.

Наконец, мы установили режим масштабирования в .aspectFill, что приведёт к масштабированию содержимого сцены для заполнения всего экрана. Затем мы вызываем метод presentScene(_:) для skView, для отображения нашей сцены.

Теперь, удалите все в файле GameScene.swift и вставьте туда следующее.

Теперь вы можете протестировать проект, вы должны увидеть чёрный-чёрный экран. На следующем этапе мы добавим содержимое в наши сцены.

3. Переменные и константы

Добавьте следующий код в начало класса GameScene прямо под наследованием GameScene от SKScene.

Здесь мы создаем ряд SKSpriteNodes. SKSpriteNodes используются для создания цветного сплетения (node) или, что более вероятно из SKTexture и это чаще всего является изображением. Мы используем удобный инициализатор init(color:size:) для создания прозрачной цветной ноды moneyContainer. В moneyContainer будут отображаться деньги, которые ставит игрок и в конце каждого раунда мы будем анимировать их перемещение, когда кто-то выигрывает игру. Разместив все деньги в одном этом месте, позволит легко анимировать все деньги за один раз.

Далее, мы добавляем константы dealBtn, hitBtn, и standBtn. Как видно из названий, они будут использоваться в игре для "deal/раздать", "hit/ещё", и "stand/достаточно" соответственно. Мы используем удобный инициализатор init(imageNamed:), который принимает название изображения без расширения, как параметр.

Затем мы создаем три константы money10, money25, и money50, с типом Money. Money — пользовательский класс который дополняет SKSpriteNode и в зависимости от типа moneyValue, создает один из трёх различных типов денег и передаёт это как параметр. Параметр moneyValue имеет тип — MoneyValue, который является enum. Взгляните на класс Money в репозитории проекта GitHub, чтобы понять как всё это работает.

Потом мы создаем SKLabelNode используя инициализатор init(text:), который принимает в качестве параметра текст, который будет показан как лейбл.

4. Реализация setupTable

Добавьте следующее после функции didMove(to:).

Здесь мы инициализируем константу стола — table и добавляем ее в сцену, используя addChild(_:), который принимает в качестве параметра ноду, добавляемую к сцене. Мы устанавливаем position (позицию) table в сцене и устанавливаем zPosition в -1. Свойство zPosition определяет порядок, в котором отрисовываются ноды. Отрисовка происходит от меньшего числа к большему. И, так как стол table должен быть позади всего, мы задаем позицию zPosition в -1.

Так же добавим в сцену moneyContainer (контейнер для денег) и instructionText (текст со справкой). Установим цвет шрифта fontColor для instructionText как черный (по умолчанию он белый).

Обновите didMove(to:) до следующего состояния.

Метод didMove(to:) вызывается сразу после того, как сцена отображается в представлении. Как правило, здесь вы выполняете настройку сцены и создаете свои активы. Если вы запустите приложение сейчас, вы должны увидеть добавленные table и instructionText. moneyContainer тоже там, но вы не можете его видеть, потому что мы создали его бесцветным.

5. Реализация setupMoney

Добавьте следующее после метода setupTable

Здесь мы просто добавляем элемент денег и настраиваем его позицию. Вызываем этот метод внутри didMove(to:).

6. Реализация setupButtons

Добавьте следующее, после метода setupMoney, который вы создали на предыдущем шаге.

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

Теперь вызываем эти методы внутри didMove(to:).

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

7. Реализация touchesBegan

Нам нужно добавить метод touchesBegan(_:with:), чтобы определить какие объекты в сцене были нажаты. Этот метод срабатывает, когда один или более палец касаются экрана. Добавьте следующее внутри touchesBegan.

По умолчанию, свойство представления сцены multiTouchEnabled установлено в false, что означает, что представление принимает только первое касание из последовательности мульти-касания. Если это свойство отключено, вы можете получить касание с помощью вычисленного свойства first из набора касаний, так как в наборе есть только один объект.

Мы можем получить расположение касания touchLocation внутри сцены, по свойству касания location. Таким образом, мы узнаем какая нода была нажата вызовом atPoint(_:) и передавая touchLocation.

Мы проверяем, если свойство названия touchedNode равно "money", и был ли нажат один из трёх экземпляров денег. Мы инициализируем константу денег — money уменьшением touchedNode до Money, затем мы вызываем метод bet, вызывающий метод getValue() для константы денег money.

8. Реализация ставки bet

Добавьте следующее после функции setupButtons, которую вы создали на предыдущем шаге.

Для начала нам надо убедиться, что пользователь не пытается поставить больше денег, чем у него есть, если пытается, то просто возвращяем ответ функции. В противном случае мы добавляем ставку — betAmount в "горшок" — pot, создаем временную константу tempMoney, устанавливаем anchorPoint в (0,0), и добавляем это в moneyContainer. Затем задаем позицию — position и скрываем dealBtn, установив свойство isHidden в false.

У SKSpriteNode есть свойство anchorPoint, которое по умолчанию установлено на (0.5,0.5). Системные координаты (0,0) располагаются слева внизу и (1,1) находится сверху справа. Вы можете изменить значения по умолчанию, если вы хотите передвинуть SKSpriteNode и хотели бы разместить его в другом месте. Например, если вы изменили свойство anchorPoint на (0,0), тогда SKSpriteNode будет вращаться относительно своего нижнего левого угла. Вы часто будете изменять это свойство чтобы настроить его расположение, то как мы и делаем.

Нам нужно создать образцы классов Pot и Player, чтобы этот код заработал. Добавьте следующее к остальным константам и переменным.

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

9. Реализация раздачи deal

Добавьте следующее к остальным вашим константами и переменным.

Массив allCards будет использоваться для хранения всех карт в игре. Это позволит легко перебирать и удалять их из сцены за один прием. Константы dealerCardsY и playerCardsY — это позиции карт по оси Y. Это поможет нам при размещении новых карт. currentPlayerType —  используется, чтобы указать, кому следующему сдавать. Оно будет равно либо dealer, либо player1.

Добавим следующее в didMove(to:).

В предыдущем коде, мы инициализировали currentPlayerType к неназванному экземпляру класса Player. Здесь мы задаем его, как player1.

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

Теперь, мы можем применить функцию раздачи (deal). Добавьте следующее под методом bet.

Этот метод довольной большой, но необходимо осуществить процесс раздачи. Давайте разберем пошагово. Мы задаём константу tempCard на экземпляр класса Card, настраиваем расположение и добавляем в сцену. Нам нужно понизить эту карту в позиции zPosition ниже 0, потому что первая карта крупье должны быть 0. Установим ее в произвольное число —100 подойдёт. Так же мы создаём константу newCard, вызывая метод getTopCard() от deck.

Затем мы добавляем  две переменные, whichPosition и whichHand, а затем запускаем некоторую логику, чтобы определить их окончательные значения. Затем мы добавляем newCard в соответствующую руку (либо игрока, либо крупье). Константа xPos определяет конечную позицию по Х карты, когда анимация закончится.

Класс SKAction имеет несколько классовых методов, которые вы можете вызвать для изменения свойств ноды, таких как позиция, масштаб и поворот. Здесь мы вызываем метод move(to:duration:), который переместит ноду из одной позиции в другую. Однако, чтобы на самом деле выполнить SKAction, вам нужно вызвать для ноды метод run(_:) и передать в SKAction в качестве параметра. Здесь, однако, мы вызываем метод run(_:completion:), который приведет к тому, что код внутри завершения "замыкания" будет запущен после того, как действие завершит своё выполнение.

После того, как действие выполнено до конца, мы разрешаем игроку делать ставки, вызывая setCanBet(canBet:) на экземпляре player1. Затем мы проверяем, является ли currentPlayerType экземпляром Dealer, и удостоверимся, что у крупье/dealer только одна карта, вызвав hand.getLength(). Если это так, мы устанавливаем первую карту крупье/dealer, которая нам понадобится в конце игры.

Поскольку первая карта у dealer всегда обращена лицом вниз до конца игры, нам нужна ссылка на первую карту, чтобы мы могли показать ее позже. Мы добавляем эту карту в массив allCards, таким образом мы можем убрать её позже, затем зададим свойству zPosition значение 0, так как эта карта должна быть под всеми остальными картами. (Помните, что позиция других карт равна 100).

Если currentPlayerType не является экземпляром Dealer, и длина (length) руки не равна 1, тогда мы выбираем tempCard и размещаем newCard в той же позиции, убедившись что zPosition равно 100.

По правилам блэкджека, для начала игры крупье и игрок получают по две карты. Тут же мы проверяем тип игрока — currentPlayerType и меняем его на противоположный. Так как у крупье меньше двух карт, мы снова вызываем функцию раздачи — deal. В противном случае, мы проверяем, что dealer и player1 имеют по две карты, и в этом случае, мы проверяем имеет ли какая-нибудь пара карт выигрышную комбинацию — 21. Если у кого-то 21, то игра закончена, потому что это называется блэкджек. Если ни у кого нет 21, тогда мы показываем кнопки standBtn и hitBtn и игра продолжается.

Правилами блэкджека установлено, что dealer должен остановиться на 17 или выше. Следующие строчки кода проверяют если у dealer'а на руках меньше 17, когда вызывается метод dealer. Если у него 17 или выше, игра окончена. Или же, если у игрока player1 на руках значение больше 21, тогда игра окончена из-за перебора.

Мы прошли через логическую цепочку. Если что-то неясно, то просто почитайте ещё раз не торопясь, чтобы лучше понять.

Теперь нам нужно добавить метод gameover.

Нам нужно знать, когда пользователь нажал кнопку раздачи. Добавьте следующий код в метод touchesBegan(_:with:).

10. Реализация окончания игры doGameOver

Далее, добавьте следующее после метода deal, который вы создали на предыдущем шаге.

Мы получаем координаты x и y первой карты в массиве allCards, которая является первой картой крупье. Затем, мы создаем экземпляр константы tempCard, вызовом getFirstCard от крупье. Помните ранее мы установили эту карту Card в методе раздачи? Теперь, мы добавляем её в сцену, задаем её координаты, используя константы tempCardX и tempCardY и устанавливаем её zPosition в 0, таким образом она оказывается под другими картами.

Нам нужно узнать кто выиграл игру, поэтому задаем переменную победитель — winner и задаем ей значение равное player1, тем не менее, оно может поменяться, если на самом деле игру выиграл dealer.

Затем мы проходим через некоторую логическую цепочку, чтобы определить кто выиграл игру. Если параметр hasBlackjack является истиной — true, тогда мы выясняем кто выиграл и возвращаем ответ от этой функции. В противном случае, мы продолжаем логическую цепочку, чтобы определить кто выиграл игру. Я не собираюсь записывать всю логику шаг за шагом, так как и так довольно понятно. Вне зависимости от того кто выиграл, мы вызываем moveMoneyContainer(position:), которая принимает в качестве параметра позицию перемещения денег в контейнер. Это будет позиция Y либо карты крупье dealer, либо игрока player1.

11. Реализация moveMoneyContainer

 Добавьте следующий код после метода doGameOver.

Метод moveMoneyContainer(position:) перемещает деньги кона — moneyContainer, к тому кто выиграл игру, игроку или крупье. Когда SKAction  завершается, мы вызываем resetMoneyContainer.

12. Реализация сброса кона resetMoneyContainer

Метод resetMoneyContainer убирает все деньги вызывая метод removeAllChildren(), сбрасывает кон — moneyContainer к его первоначальной позиции, и запускает newGame.

13. Реализация newGame

Добавьте следующие после метода resetMoneyContainer, который мы реализовали на предыдущем шаге.

Теперь, мы сбрасываем все необходимые переменные и убираем все карты из сцены, пройдя через массив allCards и вызвав removeFromParent() для каждого элемента.

14. Реализация hitBtn и standBtn

Теперь осталось завершить нашу игру, добавив обработку касаний для hitBtn и standBtn. Добавьте следующее в метод touchesBegan(_:with:).

И теперь мы реализуем методы, вызываемые в обработчике событий. Добавьте следующие два метода ниже метода newGame.

Методом hit мы гарантируем, что игрок может делать ставки, и если это так, мы устанавливаем currentPlayerType в player1, а затем вызываем метод deal и останавливаем дальнейшую ставку.

В методе stand мы вызываем setYielding для player1, передавая true. Затем мы проверяем, не превышают ли карты на руках у крупье/dealer значение 17, и в этом случае, мы вызываем раздачу, а если на руках у крупье/dealer - 17 или больше, это означает, что игра окончена.

Теперь вы можете попробовать завершенную игру.

Заключение

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

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

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.