Создание мира

В Приключении существует только один уровень, но это заполнено множеством спрайтов, включая деревья, полости, порождающие гоблинов, горящие факелы и символ босса. В запуске приложения мы загружаем все совместно используемые ресурсы, необходимые сцене, и затем создаем мир путем добавления в фоновом режиме мозаик, спрайтов и стен коллизии. Рисунок 3-1 показывает схему этого процесса.

Рисунок 3-1  , Создающий мир

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

Приключение загружает активы асинхронно

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

Создание сцены

Для цели OS X, APAAppDelegateOSX объект ответственен за загрузку сцены в запуске. В версии iOS это - ответственность APAViewController объект viewWillAppear: метод, но код является по существу тем же.

Мы загружаем активы асинхронно; это означает, что мы можем вывести на экран игровой логотип и вращающийся индикатор хода выполнения, в то время как файлы загружаются от диска. Когда загрузка завершена, мы используем обработчик завершения, чтобы создать экземпляр сцены и добавить его к SKView экземпляр в пользовательском интерфейсе приложения.

  1. Adventure: APAAppDelegateOSX.m -applicationDidFinishLaunching:
  2. - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
  3. ... (Show the progress indicator)
  4. [APAAdventureScene loadSceneAssetsWithCompletionHandler:^{
  5. APAAdventureScene *scene = [APAAdventureScene sceneWithSize:CGSizeMake(1024, 768)];
  6. [self.skView presentScene:scene];
  7. ... (Hide the progress indicator)
  8. ... (Show the Warrior/Archer buttons)
  9. [scene configureGameControllers];
  10. }];
  11. ... (Show debug info)
  12. }
9

Вызовы обработчика завершения configureGameControllers проверять на любые сконфигурированные устройства игрового контроллера, как описано в Поддержке Внешних Игровых контроллеров.

Загрузка активов сцены

loadSceneAssetsWithCompletionHandler: метод реализован APAMultiplayerLayeredCharacterSceneкласс. Это использует dispatch_async() вызывать loadSceneAssets метод в фоновом режиме; когда загрузка актива завершена, она перезванивает обработчику завершения на основном потоке.

loadSceneAssets метод переопределяется APAAdventureScene загрузить специфичные для сцены активы, включая предварительно сконфигурированные эмиттеры частицы.

  1. Adventure: APAAdventureScene.m +loadSceneAssets
  2. + (void)loadSceneAssets {
  3. sSharedProjectileSparkEmitter = [SKEmitterNode apa_emitterNodeWithEmitterNamed:@"ProjectileSplat"];
  4. ... (Load other emitters and sprites)
  5. [self loadWorldTiles];
  6. [APACave loadSharedAssets];
  7. ... (Load other characters' assets)
  8. }
3

Каждый эмиттерный узел загружается с помощью apa_emitterNodeWithEmitterNamed:, вспомогательный метод, реализованный в категории на SKEmitterNode читать в узле из заархивированного файла на диске.

После загрузки эмиттеров частицы, вызовов метода loadWorldTiles. Сцена имеет очень большой, квадратный фон (4096 x 4 096 пикселей). Вместо того, чтобы просить, чтобы GPU загрузил это все изображение, когда мы только когда-либо должны рисовать небольшую часть фона, мы разделили текстуру на сетку 32 x 32 мозаики, как показано на рисунке 3-2, приводящем к 1 024 отдельным мозаикам.

Рисунок 3-2  , Делящий фоновое изображение

Мы, возможно, сохранили эти мозаики как отдельные файлы, но это будет означать загружать 1 024 файла из диска и приводить к большому количеству вызовов рендеринга GPU, поскольку каждая мозаика должна была бы быть нарисована отдельно. Вместо этого мы храним изображения в a Tiles.atlas папка в проекте. Во время компиляции XCode использует эту папку для создания атласа текстуры, разделяя эти 1 024 мозаики через 5 изображений, показанных на рисунке 3-3. А также минимизация числа файлов, которые мы должны загрузить, это также, означает, что видимая часть фона нарисована только с одним или двумя вызовами рендеринга GPU.

Рисунок 3-3  пять фоновых мозаик текстурирует изображения атласа

loadWorldTiles метод создает SKSpriteNode экземпляр для каждого изображения и устанавливают относительная позиция так, чтобы мозаики были размечены правильно, когда добавлено к миру позже, во время инициализации сцены. После того, как мозаики загружаются, loadSharedSceneAssets вызовы метода loadSharedAssets метод на каждом классе символов используется сценой.

Загрузка совместно используемых символьных активов

Большинство классов символов в Приключении инстанцируют многократно в сцене — существует много гоблинов и полостей, например, и стольких же героев, сколько существуют проигрыватели. Для минимизации объема потребляемой памяти каждый класс символов совместно использует ряд активов. Для Приключения мы предположили, что эти символы будут использоваться любыми дополнительными сценами, которые Вы могли бы добавить к игре, таким образом, активы загружаются один раз и остаются в памяти, пока работает игра.

Каждый класс символов loadSharedAssets метод использует a dispatch_once блок, чтобы гарантировать, что код выполняется только один раз. APABoss класс реализует этот метод для загрузки, кадры анимации раньше анимировали босса уровня, когда неактивный, обход, атаку, быть пораженным или смерть; когда снаряд героя поражает босса, это также загружает создаваемый из XCode эмиттер частицы и флэш-память colorize действие для показа ущерба.

  1. Adventure: APABoss.m +loadSharedAssets
  2. + (void)loadSharedAssets {
  3. [super loadSharedAssets];
  4. static dispatch_once_t onceToken;
  5. dispatch_once(&onceToken, ^{
  6. sSharedIdleAnimationFrames = APALoadFramesFromAtlas(@"Boss_Idle", @"boss_idle", kBossIdleFrames);
  7. ... (Load other animation frames)
  8. sSharedDamageEmitter = [SKEmitterNode apa_emitterNodeWithEmitterNamed:@"BossDamage"];
  9. sSharedDamageAction = [SKAction sequence:@[
  10. [SKAction colorizeWithColor:[SKColor whiteColor] colorBlendFactor:1.0 duration:0.0],
  11. [SKAction waitForDuration:0.5],
  12. [SKAction colorizeWithColorBlendFactor:0.0 duration:0.1]]];
  13. }
  14. }
7

APALoadFramesFromAtlas() функция определяется в APAGraphicsUtilities.m. Это загружает атлас текстуры из диска, извлекает именованные и пронумерованные текстуры и возвращает массив SKTexture экземпляры.

Большинство совместно используемых активов в классах Приключения сохранено в статических переменных с простым методом доступа возвратить значение.

  1. Adventure: APABoss.m -damageEmitter
  2. static SKEmitterNode *sSharedDamageEmitter = nil;
  3. - (SKEmitterNode *)damageEmitter {
  4. return sSharedDamageEmitter;
  5. }
2

A static переменная в объеме файла означает, что та же переменная совместно используется всеми экземплярами класса, но не может быть получена доступ вне этого файла.

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

Другие классы символов реализуют loadSharedAssets метод почти таким же способом как APABoss загрузить их кадры анимации, эмиттеры и совместно использованные действия.

Создание сцены

После того, как все активы загружаются, делегат приложения создает экземпляр APAAdventureScene класс. Этот класс наследовался от APAMultiplayerLayeredCharacterScene, который ответственен за конфигурирование среды любой сцены в Приключении.

Если Вы заглядываете initWithSize: метод для APAMultiplayerLayeredCharacterScene, Вы будете видеть, что это инициализирует массив для отслеживания проигрыватели.

  1. Adventure: APAMultiplayerLayeredCharacterScene.m -initWithSize:
  2. - (instancetype)initWithSize:(CGSize)size {
  3. self = [super initWithSize:size];
  4. if (self) {
  5. _players = [[NSMutableArray alloc] initWithCapacity:kNumPlayers];
  6. _defaultPlayer = [[APAPlayer alloc] init];
  7. [(NSMutableArray *)_players addObject:_defaultPlayer];
  8. for (int i = 1; i < kNumPlayers; i++) {
  9. [(NSMutableArray *)_players addObject:[NSNull null]];
  10. }
6

Один APAPlayer экземпляр создается для представления defaultPlayer.

9

NSNull экземпляры указывают пробелы для будущих проигрывателей, кто мог бы присоединиться.

10

Приключение поддерживает до 4 проигрывателей (kNumPlayers 4), таким образом, массив заканчивается с одним APAPlayer и три NSNull экземпляры.

Затем, метод создает скелетное дерево узла для сцены. Это дерево представляет основные уровни в мире. Каждый уровень имеет различное zPosition, который влияет на порядок получения его узлов в сцене. Различные уровни описаны APAWorldLayer перечисление.

  1. Adventure: APAMultiplayerLayeredCharacterScene.h
  2. typedef enum : uint8_t {
  3. APAWorldLayerGround = 0,
  4. APAWorldLayerBelowCharacter,
  5. APAWorldLayerCharacter,
  6. APAWorldLayerAboveCharacter,
  7. APAWorldLayerTop,
  8. kWorldLayerCount
  9. } APAWorldLayer;

Каждый раз, когда узел добавляется к сцене, или при создании мира или позже в игре, узел добавляется на определенный уровень с помощью addNode:atWorldLayer: метод. Это гарантирует, что фоновые мозаики являются всегда первыми, чтобы рисоваться, сопровождаться чем-либо, что должно находиться над землей, но ниже символов, сопровождаемых символами, и т.д.

Поскольку каждый уровень в сцене содержится в вершине дерева world узел, initWithSize: метод затем создает массив мировых уровней и добавляет их как дочерние элементы world узел, и затем добавляет world как дочерний элемент сцены.

  1. Adventure: APAMultiplayerLayeredCharacterScene.m -initWithSize:
  2. _world = [[SKNode alloc] init];
  3. [_world setName:@"world"];
  4. _layers = [NSMutableArray arrayWithCapacity:kWorldLayerCount];
  5. for (int i = 0; i < kWorldLayerCount; i++) {
  6. SKNode *layer = [[SKNode alloc] init];
  7. layer.zPosition = i - kWorldLayerCount;
  8. [_world addChild:layer];
  9. [(NSMutableArray *)_layers addObject:layer];
  10. }
  11. [self addChild:_world];

Заключительная часть initWithSize: метод устанавливает, «Возглавляет Дисплей» и активирует часть HUD для проигрывателя по умолчанию. Узлы HUD добавляются в отдельный дочерний элемент сцены, вне world узел. Когда мы меняем положение, это означает это world узел так, чтобы камера следовала за героем, узлы HUD, остается статичным наверху представления.

Построение сцены приключения

После APAMultiplayerLayeredCharacterScene сконфигурирован, инициализация проваливается к APAAdventureScene.

APAdventureScene класс initWithSize: метод устанавливает различные массивы, раньше отслеживал различные элементы в сцене, и затем создает уровень, и дерево отображают структуры данных.

  1. Adventure: APAAdventureScene.m -initWithSize:
  2. - (id)initWithSize:(CGSize)size {
  3. self = [super initWithSize:size];
  4. if (self) {
  5. _heroes = [[NSMutableArray alloc] init];
  6. ... (Create the other arrays)
  7. _levelMap = APACreateDataMap(@"map_collision.png");
  8. _treeMap = APACreateDataMap(@"map_foliage.png");

Когда мы начинаем добавлять спрайты в мир, уровень и древовидные карты используются позже. initWithSize: метод продолжается путем вызова buildWorld запустить процесс создания мира. Этот метод тогда использует centerWorldOnPosition: метод для установки позиции мира так, чтобы камера центрировалась на точке икры, где герой появится, когда пользователь запустит игру.

  1. Adventure: APAAdventureScene.m -initWithSize:
  2. [self buildWorld];
  3. CGPoint startPosition = self.defaultSpawnPoint;
  4. [self centerWorldOnPosition:startPosition];
  5. }
  6. return self;
  7. }

Создание мира

buildWorld метод конфигурирует основные настройки моделирования физики, и затем добавляет спрайты и устанавливает позиции запуска для символов в уровне:

  1. Adventure: APAAdventureScene.m -buildWorld
  2. - (void)buildWorld {
  3. self.physicsWorld.gravity = CGVectorMake(0.0f, 0.0f);
  4. self.physicsWorld.contactDelegate = self;
  5. [self addBackgroundTiles];
  6. [self addSpawnPoints];
  7. [self addTrees];
  8. [self addCollisionWalls];
  9. }
3

Механизм физики Набора Sprite моделирует силу тяжести для игр, куда, например, символы работают и перепрыгивают через препятствия. Но, Приключение является игрой, где Вы смотрите вертикально вниз сверху, таким образом, нет никакой потребности в силе тяжести. Мы устанавливаем вектор силы тяжести в {0.0f, 0.0f} не указать силу тяжести.

4

Приключение действительно использует механизм физики Набора Sprite для обработки коллизий автоматически так, чтобы, например, символ не мог идти через стену или идти поверх полости. Мы устанавливаем contactDelegate к сцене для получения обратных вызовов, когда происходят определенные коллизии; они покрыты подробно в Обработке Коллизий.

addBackgroundTiles метод просто идет через предварительно загруженные узлы мозаики и добавляет их к миру; остальная часть методов обсуждена в следующих разделах.

Чтение карты уровня

Чтобы упростить конфигурировать стартовые позиции для различных символов в игре, мы создали карту уровня, показанную на рисунке 3-4. Эта карта является a .png файл, использующий цвета на четыре пикселя для указания различных элементов в игре:

  • Прозрачный пиксель представляет расположение босса уровня.

  • Красный пиксель указывает стену.

  • Зеленый пиксель указывает полость гоблина.

  • Синий пиксель указывает стартовое расположение героя.

Эта стратегия упрощает для разработчика уровня фокусироваться на игровых иллюстрациях, не имея необходимость волноваться о проблемах, таких как точные экранные размерности.

Рисунок 3-4  карта данных уровня

addSpawnPoints метод использует _levelMap это было загружено ранее путем вызова APACreateDataMap() функция от initWithSize:.

APACreateDataMap() определяется в APAGraphicsUtilities.m. Это загружает и вовлекает изображение в растровый контекст ARGB. Если Вы прослеживаете через APACreateDataMap() функция, Вы будете видеть, что она вызывает APACreateARGBBitmapContext(), который также определяется в APAGraphicsUtilities.m, создать контекст, имеющий 8 битов за компонент.

  1. Adventure: APAGraphicsUtilities.m APACreateARGBBitmapContext()
  2. context = CGBitmapContextCreate(bitmapData,
  3. pixelsWide,
  4. pixelsHigh,
  5. 8, // bits per component
  6. bitmapBytesPerRow,
  7. colorSpace,
  8. (CGBitmapInfo)kCGImageAlphaPremultipliedFirst);

Если Вы заглядываете APAGraphicsUtilities.h, Вы будете видеть что APADataMap структура определяется с помощью четыре uint8_t поля:

  1. Adventure: APAGraphicsUtilities.h
  2. typedef struct {
  3. uint8_t bossLocation, wall, goblinCaveLocation, heroSpawnLocation;
  4. } APADataMap;

Данные «на 4 x 8 бит на пиксель» в контексте ARGB переводят непосредственно в эти четыре поля в APADataMap структура.

APACreateDataMap() функционируйте просто возвращает указатель на необработанные данные, но APAAdventureScene доступы класса, что данные путем обработки его как массива C APADataMap структуры. Это означает, что код проще считать, потому что мы обращаемся к .goblinCaveLocation вместо того, чтобы смотреть на необработанные байты для “зеленого канала”.

Для перевода пикселей на уровне отображаются в координаты сцены, addSpawnPoints циклы метода через APADataMap использование структур вкладывается for циклы. Каждый проходит через проверки цикла пиксель APADataMap видеть, должно ли что-нибудь быть создано в текущем расположении.

  1. Adventure: APAAdventureScene.m -addSpawnPoints
  2. - (void)addSpawnPoints {
  3. for (int y = 0; y < kLevelMapSize; y++) {
  4. for (int x = 0; x < kLevelMapSize; x++) {
  5. CGPoint location = CGPointMake(x, y);
  6. APADataMap spot = [self queryLevelMap:location];
  7. CGPoint worldPoint = [self convertLevelMapPointToWorldPoint:location];
  8. if (spot.boss <= 200) {
  9. ... (Create a boss at this location)
  10. }
  11. if (spot.goblinCaveLocation >= 200) {
  12. ... (Create a goblin cave at this location)
  13. }
  14. if (spot.heroSpawnLocation >= 200) {
  15. ... (Set the hero spawn point)
  16. }
  17. }
  18. }
  19. }
3

kLevelMapSize постоянный 256 — высота и ширина изображения карты уровня. Каждый из этих пикселей переводит в координату в сцене через серию вспомогательных методов, преобразовывающих между различными системами координат.

9

Расположение икры босса представлено альфа-каналом. При рассмотрении очень близко карты на рисунке 3-4 Вы будете видеть один прозрачный пиксель в конце лабиринта, который является, почему код проверяет, является ли альфа-значение меньше чем 200.

12

Полости гоблина представлены зелеными пикселями в карте коллизии. Любой пиксель с зеленым значением цвета 200 или больше переводит в APACave экземпляр в игре.

15

Расположение икры героя представлено значением, больше, чем 200 в синем канале пикселя.

18

(Красный канал используется для указания стен лабиринта, как описано в Добавлении Стен Коллизии),

Чтение древовидной карты

Расположения деревьев в сцене указаны в карте листвы, показанной на рисунке 3-5.

Рисунок 3-5  карта данных листвы

Эта карта снова переводится в данные с помощью APACreateDataMap() функция, но на сей раз APAAdventureScene интерпретирует данные как массив APATreeMap структуры.

В карте листвы альфа и синие каналы не использованы, но красные и зеленые каналы используются следующим образом:

  • Красный канал указывает маленькое дерево.

  • Зеленый канал указывает большое дерево.

  1. Adventure: APAGraphicsUtilities.h
  2. typedef struct {
  3. uint8_t unusedA, bigTreeLocation, smallTreeLocation, unusedB;
  4. } APATreeMap;

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

Добавление стен коллизии

addCollisionWalls метод использует данные, закодированные в красном канале каждого пикселя в карте уровня для определения, куда поместить стены в лабиринт. Код в этом методе является довольно плотным, и таким образом, Вы могли бы счесть его несколько неинтуитивным, если Вы не привыкли к сырым данным C побитовая обработка.

  1. Adventure: APAAdventureScene.m -addCollisionWalls
  2. - (void)addCollisionWalls {
  3. unsigned char *filled = alloca(kLevelMapSize * kLevelMapSize);
  4. memset(filled, 0, kLevelMapSize * kLevelMapSize);
  5. ...
3

alloca() вызов выделяет блок памяти, достаточно большой для содержания пикселей в карте изображения.

4

Этот блок тогда заполнен нулями.

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

Каждый цикл идет через все пиксели в карте коллизии, ища группы последовательных пикселей чей wall (красное) значение канала больше, чем 200. Каждый последовательный блок тогда превращен в стену коллизии с помощью addCollisionWallAtWorldPoint:withWidth:height: метод, добавляющий основной спрайт со сконфигурированной, прямоугольной организацией физики.

  1. Adventure: APAAdventureScene.m -addCollisionWallAtWorldPoint:withWidth:height:
  2. - (void)addCollisionWallAtWorldPoint:(CGPoint)worldPoint withWidth:(CGFloat)width height:(CGFloat)height {
  3. CGRect rect = CGRectMake(0, 0, width, height);
  4. SKNode *wallNode = [SKNode node];
  5. wallNode.position = CGPointMake(worldPoint.x + rect.size.width * 0.5, worldPoint.y - rect.size.height * 0.5);
  6. wallNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:rect.size];
  7. wallNode.physicsBody.dynamic = NO;
  8. wallNode.physicsBody.categoryBitMask = APAColliderTypeWall;
  9. [self addNode:wallNode atWorldLayer:APAWorldLayerGround];
  10. }
3

Организация физики является простой, нединамической, прямоугольной организацией. Когда другие спрайты в игре сталкиваются со стеной, они автоматически предотвращены от нарушения границы в прямоугольной области, дав иллюзию обхода через лабиринт стен.

9

categoryBitMask поскольку стена APAColliderTypeWall. Мы используем это значение, чтобы установить, сталкиваются ли организации физики одного типа с организациями физики другого типа; коллизии покрыты более подробно в Обработке Коллизий.

Как только стены добавляются, мир завершен!