Создание мира
В Приключении существует только один уровень, но это заполнено множеством спрайтов, включая деревья, полости, порождающие гоблинов, горящие факелы и символ босса. В запуске приложения мы загружаем все совместно используемые ресурсы, необходимые сцене, и затем создаем мир путем добавления в фоновом режиме мозаик, спрайтов и стен коллизии. Рисунок 3-1 показывает схему этого процесса.
Как только активы загружаются, мы создаем экземпляр сцены. Во время инициализации сцены мы создаем и устанавливаем начальные позиции для всех узлов, необходимых для уровня. Наконец, когда инициализация завершена, мы добавляем сцену в SKView
, в которой точке представление представляет содержание на экран.
Приключение загружает активы асинхронно
Существует большое количество атласа текстуры и файлов эмиттера частицы, используемых сценой Приключения. Вместо того, чтобы загружать все эти активы каждый раз, они необходимы, или даже лениво как раз в то самое время, когда они сначала необходимы, Приключение использует асинхронный механизм загрузки для загрузки всего один раз, когда запускается игра. Это избегает проблем во время геймплея, таких как прерывания или отброшенные кадры, вызванные путем чтения файлов из диска.
Создание сцены
Для цели OS X, APAAppDelegateOSX
объект ответственен за загрузку сцены в запуске. В версии iOS это - ответственность APAViewController
объект viewWillAppear:
метод, но код является по существу тем же.
Мы загружаем активы асинхронно; это означает, что мы можем вывести на экран игровой логотип и вращающийся индикатор хода выполнения, в то время как файлы загружаются от диска. Когда загрузка завершена, мы используем обработчик завершения, чтобы создать экземпляр сцены и добавить его к SKView
экземпляр в пользовательском интерфейсе приложения.
Adventure: APAAppDelegateOSX.m -applicationDidFinishLaunching:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
... (Show the progress indicator)
[APAAdventureScene loadSceneAssetsWithCompletionHandler:^{
APAAdventureScene *scene = [APAAdventureScene sceneWithSize:CGSizeMake(1024, 768)];
[self.skView presentScene:scene];
... (Hide the progress indicator)
... (Show the Warrior/Archer buttons)
[scene configureGameControllers];
}];
... (Show debug info)
}
Загрузка активов сцены
loadSceneAssetsWithCompletionHandler:
метод реализован APAMultiplayerLayeredCharacterScene
класс. Это использует dispatch_async()
вызывать loadSceneAssets
метод в фоновом режиме; когда загрузка актива завершена, она перезванивает обработчику завершения на основном потоке.
loadSceneAssets
метод переопределяется APAAdventureScene
загрузить специфичные для сцены активы, включая предварительно сконфигурированные эмиттеры частицы.
Adventure: APAAdventureScene.m +loadSceneAssets
+ (void)loadSceneAssets {
sSharedProjectileSparkEmitter = [SKEmitterNode apa_emitterNodeWithEmitterNamed:@"ProjectileSplat"];
... (Load other emitters and sprites)
[self loadWorldTiles];
[APACave loadSharedAssets];
... (Load other characters' assets)
}
Каждый эмиттерный узел загружается с помощью apa_emitterNodeWithEmitterNamed:
, вспомогательный метод, реализованный в категории на SKEmitterNode
читать в узле из заархивированного файла на диске.
После загрузки эмиттеров частицы, вызовов метода loadWorldTiles
. Сцена имеет очень большой, квадратный фон (4096 x 4 096 пикселей). Вместо того, чтобы просить, чтобы GPU загрузил это все изображение, когда мы только когда-либо должны рисовать небольшую часть фона, мы разделили текстуру на сетку 32 x 32 мозаики, как показано на рисунке 3-2, приводящем к 1 024 отдельным мозаикам.
Мы, возможно, сохранили эти мозаики как отдельные файлы, но это будет означать загружать 1 024 файла из диска и приводить к большому количеству вызовов рендеринга GPU, поскольку каждая мозаика должна была бы быть нарисована отдельно. Вместо этого мы храним изображения в a Tiles.atlas
папка в проекте. Во время компиляции XCode использует эту папку для создания атласа текстуры, разделяя эти 1 024 мозаики через 5 изображений, показанных на рисунке 3-3. А также минимизация числа файлов, которые мы должны загрузить, это также, означает, что видимая часть фона нарисована только с одним или двумя вызовами рендеринга GPU.
loadWorldTiles
метод создает SKSpriteNode
экземпляр для каждого изображения и устанавливают относительная позиция так, чтобы мозаики были размечены правильно, когда добавлено к миру позже, во время инициализации сцены. После того, как мозаики загружаются, loadSharedSceneAssets
вызовы метода loadSharedAssets
метод на каждом классе символов используется сценой.
Загрузка совместно используемых символьных активов
Большинство классов символов в Приключении инстанцируют многократно в сцене — существует много гоблинов и полостей, например, и стольких же героев, сколько существуют проигрыватели. Для минимизации объема потребляемой памяти каждый класс символов совместно использует ряд активов. Для Приключения мы предположили, что эти символы будут использоваться любыми дополнительными сценами, которые Вы могли бы добавить к игре, таким образом, активы загружаются один раз и остаются в памяти, пока работает игра.
Каждый класс символов loadSharedAssets
метод использует a dispatch_once
блок, чтобы гарантировать, что код выполняется только один раз. APABoss
класс реализует этот метод для загрузки, кадры анимации раньше анимировали босса уровня, когда неактивный, обход, атаку, быть пораженным или смерть; когда снаряд героя поражает босса, это также загружает создаваемый из XCode эмиттер частицы и флэш-память colorize действие для показа ущерба.
Adventure: APABoss.m +loadSharedAssets
+ (void)loadSharedAssets {
[super loadSharedAssets];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sSharedIdleAnimationFrames = APALoadFramesFromAtlas(@"Boss_Idle", @"boss_idle", kBossIdleFrames);
... (Load other animation frames)
sSharedDamageEmitter = [SKEmitterNode apa_emitterNodeWithEmitterNamed:@"BossDamage"];
sSharedDamageAction = [SKAction sequence:@[
[SKAction colorizeWithColor:[SKColor whiteColor] colorBlendFactor:1.0 duration:0.0],
[SKAction waitForDuration:0.5],
[SKAction colorizeWithColorBlendFactor:0.0 duration:0.1]]];
}
}
APALoadFramesFromAtlas()
функция определяется в APAGraphicsUtilities.m
. Это загружает атлас текстуры из диска, извлекает именованные и пронумерованные текстуры и возвращает массив SKTexture
экземпляры.
Большинство совместно используемых активов в классах Приключения сохранено в статических переменных с простым методом доступа возвратить значение.
Adventure: APABoss.m -damageEmitter
static SKEmitterNode *sSharedDamageEmitter = nil;
- (SKEmitterNode *)damageEmitter {
return sSharedDamageEmitter;
}
A static
переменная в объеме файла означает, что та же переменная совместно используется всеми экземплярами класса, но не может быть получена доступ вне этого файла.
После того, как актив загружается, это остается в памяти для использования любым будущим экземпляром класса.
Другие классы символов реализуют loadSharedAssets
метод почти таким же способом как APABoss
загрузить их кадры анимации, эмиттеры и совместно использованные действия.
Создание сцены
После того, как все активы загружаются, делегат приложения создает экземпляр APAAdventureScene
класс. Этот класс наследовался от APAMultiplayerLayeredCharacterScene
, который ответственен за конфигурирование среды любой сцены в Приключении.
Если Вы заглядываете initWithSize:
метод для APAMultiplayerLayeredCharacterScene
, Вы будете видеть, что это инициализирует массив для отслеживания проигрыватели.
Adventure: APAMultiplayerLayeredCharacterScene.m -initWithSize:
- (instancetype)initWithSize:(CGSize)size {
self = [super initWithSize:size];
if (self) {
_players = [[NSMutableArray alloc] initWithCapacity:kNumPlayers];
_defaultPlayer = [[APAPlayer alloc] init];
[(NSMutableArray *)_players addObject:_defaultPlayer];
for (int i = 1; i < kNumPlayers; i++) {
[(NSMutableArray *)_players addObject:[NSNull null]];
}
Один APAPlayer
экземпляр создается для представления defaultPlayer
.
NSNull
экземпляры указывают пробелы для будущих проигрывателей, кто мог бы присоединиться.
Приключение поддерживает до 4 проигрывателей (kNumPlayers
4), таким образом, массив заканчивается с одним APAPlayer
и три NSNull
экземпляры.
Затем, метод создает скелетное дерево узла для сцены. Это дерево представляет основные уровни в мире. Каждый уровень имеет различное zPosition
, который влияет на порядок получения его узлов в сцене. Различные уровни описаны APAWorldLayer
перечисление.
Adventure: APAMultiplayerLayeredCharacterScene.h
typedef enum : uint8_t {
APAWorldLayerGround = 0,
APAWorldLayerBelowCharacter,
APAWorldLayerCharacter,
APAWorldLayerAboveCharacter,
APAWorldLayerTop,
kWorldLayerCount
} APAWorldLayer;
Каждый раз, когда узел добавляется к сцене, или при создании мира или позже в игре, узел добавляется на определенный уровень с помощью addNode:atWorldLayer:
метод. Это гарантирует, что фоновые мозаики являются всегда первыми, чтобы рисоваться, сопровождаться чем-либо, что должно находиться над землей, но ниже символов, сопровождаемых символами, и т.д.
Поскольку каждый уровень в сцене содержится в вершине дерева world
узел, initWithSize:
метод затем создает массив мировых уровней и добавляет их как дочерние элементы world
узел, и затем добавляет world
как дочерний элемент сцены.
Adventure: APAMultiplayerLayeredCharacterScene.m -initWithSize:
_world = [[SKNode alloc] init];
[_world setName:@"world"];
_layers = [NSMutableArray arrayWithCapacity:kWorldLayerCount];
for (int i = 0; i < kWorldLayerCount; i++) {
SKNode *layer = [[SKNode alloc] init];
layer.zPosition = i - kWorldLayerCount;
[_world addChild:layer];
[(NSMutableArray *)_layers addObject:layer];
}
[self addChild:_world];
Заключительная часть initWithSize:
метод устанавливает, «Возглавляет Дисплей» и активирует часть HUD для проигрывателя по умолчанию. Узлы HUD добавляются в отдельный дочерний элемент сцены, вне world
узел. Когда мы меняем положение, это означает это world
узел так, чтобы камера следовала за героем, узлы HUD, остается статичным наверху представления.
Построение сцены приключения
После APAMultiplayerLayeredCharacterScene
сконфигурирован, инициализация проваливается к APAAdventureScene
.
APAdventureScene
класс initWithSize:
метод устанавливает различные массивы, раньше отслеживал различные элементы в сцене, и затем создает уровень, и дерево отображают структуры данных.
Adventure: APAAdventureScene.m -initWithSize:
- (id)initWithSize:(CGSize)size {
self = [super initWithSize:size];
if (self) {
_heroes = [[NSMutableArray alloc] init];
... (Create the other arrays)
_levelMap = APACreateDataMap(@"map_collision.png");
_treeMap = APACreateDataMap(@"map_foliage.png");
Когда мы начинаем добавлять спрайты в мир, уровень и древовидные карты используются позже. initWithSize:
метод продолжается путем вызова buildWorld
запустить процесс создания мира. Этот метод тогда использует centerWorldOnPosition:
метод для установки позиции мира так, чтобы камера центрировалась на точке икры, где герой появится, когда пользователь запустит игру.
Adventure: APAAdventureScene.m -initWithSize:
[self buildWorld];
CGPoint startPosition = self.defaultSpawnPoint;
[self centerWorldOnPosition:startPosition];
}
return self;
}
Создание мира
buildWorld
метод конфигурирует основные настройки моделирования физики, и затем добавляет спрайты и устанавливает позиции запуска для символов в уровне:
Adventure: APAAdventureScene.m -buildWorld
- (void)buildWorld {
self.physicsWorld.gravity = CGVectorMake(0.0f, 0.0f);
self.physicsWorld.contactDelegate = self;
[self addBackgroundTiles];
[self addSpawnPoints];
[self addTrees];
[self addCollisionWalls];
}
Механизм физики Набора Sprite моделирует силу тяжести для игр, куда, например, символы работают и перепрыгивают через препятствия. Но, Приключение является игрой, где Вы смотрите вертикально вниз сверху, таким образом, нет никакой потребности в силе тяжести. Мы устанавливаем вектор силы тяжести в {0.0f, 0.0f}
не указать силу тяжести.
Приключение действительно использует механизм физики Набора Sprite для обработки коллизий автоматически так, чтобы, например, символ не мог идти через стену или идти поверх полости. Мы устанавливаем contactDelegate
к сцене для получения обратных вызовов, когда происходят определенные коллизии; они покрыты подробно в Обработке Коллизий.
addBackgroundTiles
метод просто идет через предварительно загруженные узлы мозаики и добавляет их к миру; остальная часть методов обсуждена в следующих разделах.
Чтение карты уровня
Чтобы упростить конфигурировать стартовые позиции для различных символов в игре, мы создали карту уровня, показанную на рисунке 3-4. Эта карта является a .png
файл, использующий цвета на четыре пикселя для указания различных элементов в игре:
Прозрачный пиксель представляет расположение босса уровня.
Красный пиксель указывает стену.
Зеленый пиксель указывает полость гоблина.
Синий пиксель указывает стартовое расположение героя.
Эта стратегия упрощает для разработчика уровня фокусироваться на игровых иллюстрациях, не имея необходимость волноваться о проблемах, таких как точные экранные размерности.
addSpawnPoints
метод использует _levelMap
это было загружено ранее путем вызова APACreateDataMap()
функция от initWithSize:
.
APACreateDataMap()
определяется в APAGraphicsUtilities.m
. Это загружает и вовлекает изображение в растровый контекст ARGB. Если Вы прослеживаете через APACreateDataMap()
функция, Вы будете видеть, что она вызывает APACreateARGBBitmapContext()
, который также определяется в APAGraphicsUtilities.m
, создать контекст, имеющий 8 битов за компонент.
Adventure: APAGraphicsUtilities.m APACreateARGBBitmapContext()
context = CGBitmapContextCreate(bitmapData,
pixelsWide,
pixelsHigh,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
(CGBitmapInfo)kCGImageAlphaPremultipliedFirst);
Если Вы заглядываете APAGraphicsUtilities.h
, Вы будете видеть что APADataMap
структура определяется с помощью четыре uint8_t
поля:
Adventure: APAGraphicsUtilities.h
typedef struct {
uint8_t bossLocation, wall, goblinCaveLocation, heroSpawnLocation;
} APADataMap;
Данные «на 4 x 8 бит на пиксель» в контексте ARGB переводят непосредственно в эти четыре поля в APADataMap
структура.
APACreateDataMap()
функционируйте просто возвращает указатель на необработанные данные, но APAAdventureScene
доступы класса, что данные путем обработки его как массива C APADataMap
структуры. Это означает, что код проще считать, потому что мы обращаемся к .goblinCaveLocation
вместо того, чтобы смотреть на необработанные байты для “зеленого канала”.
Для перевода пикселей на уровне отображаются в координаты сцены, addSpawnPoints
циклы метода через APADataMap
использование структур вкладывается for
циклы. Каждый проходит через проверки цикла пиксель APADataMap
видеть, должно ли что-нибудь быть создано в текущем расположении.
Adventure: APAAdventureScene.m -addSpawnPoints
- (void)addSpawnPoints {
for (int y = 0; y < kLevelMapSize; y++) {
for (int x = 0; x < kLevelMapSize; x++) {
CGPoint location = CGPointMake(x, y);
APADataMap spot = [self queryLevelMap:location];
CGPoint worldPoint = [self convertLevelMapPointToWorldPoint:location];
if (spot.boss <= 200) {
... (Create a boss at this location)
}
if (spot.goblinCaveLocation >= 200) {
... (Create a goblin cave at this location)
}
if (spot.heroSpawnLocation >= 200) {
... (Set the hero spawn point)
}
}
}
}
kLevelMapSize
постоянный 256 — высота и ширина изображения карты уровня. Каждый из этих пикселей переводит в координату в сцене через серию вспомогательных методов, преобразовывающих между различными системами координат.
Расположение икры босса представлено альфа-каналом. При рассмотрении очень близко карты на рисунке 3-4 Вы будете видеть один прозрачный пиксель в конце лабиринта, который является, почему код проверяет, является ли альфа-значение меньше чем 200.
12Полости гоблина представлены зелеными пикселями в карте коллизии. Любой пиксель с зеленым значением цвета 200 или больше переводит в APACave
экземпляр в игре.
Расположение икры героя представлено значением, больше, чем 200 в синем канале пикселя.
18(Красный канал используется для указания стен лабиринта, как описано в Добавлении Стен Коллизии),
Чтение древовидной карты
Расположения деревьев в сцене указаны в карте листвы, показанной на рисунке 3-5.
Эта карта снова переводится в данные с помощью APACreateDataMap()
функция, но на сей раз APAAdventureScene
интерпретирует данные как массив APATreeMap
структуры.
В карте листвы альфа и синие каналы не использованы, но красные и зеленые каналы используются следующим образом:
Красный канал указывает маленькое дерево.
Зеленый канал указывает большое дерево.
Adventure: APAGraphicsUtilities.h
typedef struct {
uint8_t unusedA, bigTreeLocation, smallTreeLocation, unusedB;
} APATreeMap;
Деревья в Приключении создаются из уровней повторного изображения, перемещающихся в различные уровни для показа эффекта параллакса, как камера перемещается во всем мире. Этот эффект описан более подробно в Создании Эффекта Параллакса.
Добавление стен коллизии
addCollisionWalls
метод использует данные, закодированные в красном канале каждого пикселя в карте уровня для определения, куда поместить стены в лабиринт. Код в этом методе является довольно плотным, и таким образом, Вы могли бы счесть его несколько неинтуитивным, если Вы не привыкли к сырым данным C побитовая обработка.
Adventure: APAAdventureScene.m -addCollisionWalls
- (void)addCollisionWalls {
unsigned char *filled = alloca(kLevelMapSize * kLevelMapSize);
memset(filled, 0, kLevelMapSize * kLevelMapSize);
...
alloca()
вызов выделяет блок памяти, достаточно большой для содержания пикселей в карте изображения.
Этот блок тогда заполнен нулями.
addCollisionWalls
метод продолжается путем выполнения того, что похоже на ту же вещь дважды — существует два длинных блока вложенного включения кода for
циклы. Первый из этих циклов ответственен за создание горизонтальных стен; вторые дескрипторы вертикальные стены.
Каждый цикл идет через все пиксели в карте коллизии, ища группы последовательных пикселей чей wall
(красное) значение канала больше, чем 200. Каждый последовательный блок тогда превращен в стену коллизии с помощью addCollisionWallAtWorldPoint:withWidth:height:
метод, добавляющий основной спрайт со сконфигурированной, прямоугольной организацией физики.
Adventure: APAAdventureScene.m -addCollisionWallAtWorldPoint:withWidth:height:
- (void)addCollisionWallAtWorldPoint:(CGPoint)worldPoint withWidth:(CGFloat)width height:(CGFloat)height {
CGRect rect = CGRectMake(0, 0, width, height);
SKNode *wallNode = [SKNode node];
wallNode.position = CGPointMake(worldPoint.x + rect.size.width * 0.5, worldPoint.y - rect.size.height * 0.5);
wallNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:rect.size];
wallNode.physicsBody.dynamic = NO;
wallNode.physicsBody.categoryBitMask = APAColliderTypeWall;
[self addNode:wallNode atWorldLayer:APAWorldLayerGround];
}
Организация физики является простой, нединамической, прямоугольной организацией. Когда другие спрайты в игре сталкиваются со стеной, они автоматически предотвращены от нарушения границы в прямоугольной области, дав иллюзию обхода через лабиринт стен.
9 categoryBitMask
поскольку стена APAColliderTypeWall
. Мы используем это значение, чтобы установить, сталкиваются ли организации физики одного типа с организациями физики другого типа; коллизии покрыты более подробно в Обработке Коллизий.
Как только стены добавляются, мир завершен!
Вызовы обработчика завершения
configureGameControllers
проверять на любые сконфигурированные устройства игрового контроллера, как описано в Поддержке Внешних Игровых контроллеров.