Привет!
Итак, в прошлой части мы узнали, что такое git и начали создавать игровое поле. Сегодня продолжим разрабатывать поле, для чего напишем пару вспомогательных классов. На их примере мы разберем несколько протоколов и перегрузку операторов, что поможет сильно сократить объем кода и повысить его читабельность. Для тех, кто все пропустил, вот часть 1 и часть 2. Го!
С момента написания прошлого поста я решил внести немного изменений в игровое поле, например, соотношение размеров поля будет 4 х 6, чтобы оптимизировать размер узлов, так как узлы были слишком маленькими. Для этого я поменял числовое значение параметра ratio в файле FieldScene.swift на (4, 6).
Также я решил убрать StatusBar чтобы освободить больше места на экране и сконцентрировать внимание пользователя на игровом процессе. Вместо StatusBar лучше добавить верхнее меню, где будет информация о текущем количестве ресурсов и прочих игровых статусах.
Этот пункт внедрить тоже несложно:
В файле Constants.swift меняем значение MENU_HEIGHT на
let TOP_MENU_HEIGHT = CGF(50.0)
let BOTTOM_MENU_HEIGHT = CGF(70.0)
В файле Marking.swift меняем инициализатор на вот этот:
init(size: CGSize, ratio: (Int, Int)) {
let tmp_x = (size.width - MIN_SPACE * 2) / ratio.0
let tmp_y = (size.height - MIN_SPACE * 2 - TOP_MENU_HEIGHT - BOTTOM_MENU_HEIGHT) / ratio.1
self.side = min(tmp_x, tmp_y)
self.frame = (
(size.width - self.side * ratio.0) / 2,
BOTTOM_MENU_HEIGHT + ((size.height - BOTTOM_MENU_HEIGHT - TOP_MENU_HEIGHT - self.side * ratio.1) / 2)
)
}
В файле GameViewController.swift в самом начале класса (внутри) написать:
override var prefersStatusBarHidden: Bool { return true }
Теперь о вспомогательных классах, для этого в папке Grasp создаем папку Supportive, а в ней файл Supportive Classes.swift
Для начала напишем класс FID, с помощью которого будет однозначно задаваться позиция каждого нашего узла. В нем будут поля x и y, которые будут означать индексы узла по горизонтали и вертикали соответственно.
class FID {
var x : Int = Int()
var y : Int = Int()
init() {}
}
Инициализировать его можно будет по двум значениям типа Int или по паре (Int, Int)
init(_ x: Int, _ y: Int) {
self.x = x
self.y = y
}
// Черточки перед x и y означают, что эти переменные не будут отображться при вызове, например, вместо fid = FID(x: 0, y: 0) будет fid = FID(0, 0)
init(fid: (Int, Int)) {
self.x = fid.0
self.y = fid.1
}
Так как в дальнейшем мы будем работать со словарями, то нужно сделать этот класс хэшируемым, для этого нужно после названия класса написать : Hashable
И прописать в нем hashValue вот таким образом:
var hashValue: Int {
return Int(x * 1e5 + y)
}
// 1e5 = 100000
теперь нужно добавить в него оператор сравнения, чтобы наш класс удовлетворял протоколу Equatable:
static func == (l: FID, r: FID) -> Bool {
return l.x == r.x && l.y == r.y
}
В итоге наш класс выглядит так:
class FID : Hashable {
var x : Int = Int()
var y : Int = Int()
var hashValue: Int {
return Int(x * 1e5 + y)
}
static func == (l: FID, r: FID) -> Bool {
return l.x == r.x && l.y == r.y
}
init() {}
init(_ x: Int, _ y: Int) {
self.x = x
self.y = y
}
init(_ fid: (Int, Int)) {
self.x = fid.0
self.y = fid.1
}
}
А еще нам нужен класс Direction, который будет отвечать за направление, он будет очень похож на класс FID, но x и y в нем могут принимать значения только в промежутке [-1, 1] и модуль их суммы всегда равен 1, проще говоря только значения (-1, 0); (0, -1); (1, 0); (0, 1).
class Direction : Hashable {
var x : Int = Int()
var y : Int = Int()
var hashValue: Int {
return Int(x * 1e5 + y)
}
static func == (l: Direction, r: Direction) -> Bool {
return l.x == r.x && l.y == r.y
}
init() {}
init(_ x: Int, _ y: Int) {
self.x = x
self.y = y
}
}
Вместо него можно было бы использовать FID, но так как они имеют разное предназначения, мы их четко разделяем
Что ж, давайте теперь начнем делать нашы узлы, для этого создадим в паке Field папку Cell, а в ней файл Cell.swift
Клеточка имеет позицию и мы должны знать, куда из этой позиции можно перейти, для этого нам нужны будут поля класса:
var fid : FID = FID()
var ways : [Direction : Bool] = [Direction : Bool]()
Инициализировать узел будем по размеру и fid:
init(size: CGSize, fid: FID) {
super.init(texture: SKTexture(imageNamed: "Cell"), color: UIC.clear, size: size)
// эту строчку далее разберем подробнее
self.fid = fid
for i in -1...1 {
for j in -1...1 {
if (i != j) {
self.ways[Direction(i, j)] = true
}
}
}
// полагаем, что из текущего узла можно перейти во все соседние
}
Строчка
super.init(texture: SKTexture(imageNamed: "Cell"), color: UIC.clear, size: size) означает, что мы инициализируем родительский класс SKSpriteNode с помощью текстуры, определяемой изображением Cell, и заданного размера size.
Изображение Cell мы создадим с помощью фотошопа, создаем новый файл, ставим размер 512Х512, с прозрачным фоном:
В инструментах выбираем эллипс, даем ему размеры нашего изображения и центруем его:
Сохраняем в формате .png, важно чтобы не было сжатия,
Перетаскиваем получившееся изображение в Assets.xassets
На данном этапе класс Field будет выглядеть так:
class Field {
var scheme : [[Cell]] = [[Cell]]()
init() {}
// init(source: JSON) {}
init(width: Int, height: Int, cellSize: CGSize) {
for i in 0 ..< width {
var column = [Cell]()
for j in 0 ..< height {
let c = Cell(size: cellSize, fid: FID(i, j))
column.append(c)
}
scheme.append(column)
}
}
}
Мы просто заполняем массив scheme нашими узлами.
Теперь давайте визуализируем всю нашу работу, для этого перепишем файл FieldScene.swift:
class FieldScene: SKScene {
final var ratio : (Int, Int) = (4, 6)
var mark : Marking = Marking()
var field: Field = Field()
override init(size: CGSize) {
super.init(size: size)
self.mark = Marking(size: size, ratio: self.ratio)
self.backgroundColor = UIC.white
field = Field(width: ratio.0, height: ratio.1, cellSize: CGSize(width: mark.side * 0.6, height: mark.side * 0.6))
let top_menu = SKSN(color: UIC.blue, size: CGSize(width: screen.width, height: TOP_MENU_HEIGHT))
top_menu.anchorPoint = CGPoint(x: 0, y: 0)
top_menu.position = CGPoint(x: 0, y: screen.height - TOP_MENU_HEIGHT)
self.addChild(top_menu)
for var i in 0..<ratio.0 {
for var j in 0..<ratio.1 {
field.scheme[i][j].position.x = mark.frame.0 + (i + 0.5) * mark.side
field.scheme[i][j].position.y = mark.frame.1 + (j + 0.5) * mark.side
field.scheme[i][j].anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.addChild(field.scheme[i][j])
}
}
let bottom_menu = SKSN(color: UIC.darkGray, size: CGSize(width: screen.width, height: BOTTOM_MENU_HEIGHT))
bottom_menu.position = CGPoint(x: 0, y: 0)
bottom_menu.anchorPoint = CGPoint(x: 0, y: 0)
self.addChild(bottom_menu)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Здесь мы заменили StatusBar на TopMenu и стали отображать узлы как конкретные объекты из класса Field.
Запускаем, в итоге получаем такую картину:
Ну вот, стало гораздо наряднее.
В следующей части мы разбираем очень полезный инструмент под названием JSON, разнообразим наш ландшафт и создадим зачаточную версию нашей карты. А чтобы прочитать статью сразу после выхода - подписывайся на обновления, встретимся через неделю. Пока!
Другие статьи
Цикл статей о первом опыте самостоятельной разработки игры для iOS учеником Московской школы программистов. Часть 2.
В этой главе мы начнем использовать технологию git, поговорим про расширения и сделаем первые шаги по созданию нашего поля боевых действий.
Цикл статей о первом опыте самостоятельной разработки игры для iOS учеником Московской школы программистов. Начало.
"После множественных попыток и экспериментов я все-таки решил написать свою iOS-игру. В этом блоге будет рассказана поэтапная история ее создания. Скажу сразу, до этого момента я пробовал разные проекты, набивал шишки, но приложения, прошедшего путь от самого начала и до логического завершения, ещё не делал. Так что в последующих постах я расскажу вам именно первый опыт разработки полноценного продукта."
Наш юный падаван придумал, как написать iOS-приложение за 30 минут всего одной строчкой заклинания!
Итак, необходимо выполнить всего четыре простых шага:
1. Скачайте бесплатное приложение Xcode для работы с кодом.
2. Создайте новый проект в Xcode (выбрать «платформа iOS», тип приложения «Single View Application»)...