среда, 25 июля 2012 г.

iOS. Разработка игры Bricks.


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

Ниже приведен список ссылок на необходимые для создания игры файлы.

Спрайты взрывов.
http://homepage.ntlworld.com/david.howe50

Фоновые картинки.
http://openclipart.org

Звуки.

Хлопок.
http://www.pacdv.com/sounds/applause-sounds.html

Фоновая музыка.
Jake Peterson GBMusicTrack MP3.

Другие звуки.
http://www.wavsource.com/sfx/sfx3.htm
http://simplythebest.net/sounds/WAV/sound_effects_WAV

Элементы игрового экрана.

http://blog.roychowdhury.org/wp-content/uploads/2010/10/screen-elements.png

Особенности игры.
1. Игра состоит из нескольких уровней. Когда игрок разбивает все блоки на одном уровне, то она переводится на следующий уровень.
2. Уровни отличаются друг от друга фоновыми картинками и анимацией на заднем фоне. В данной версии организовано переключение между дождевой погодой и движущимися облаками. Интересной идеей будет сделать зависимость погоды на уровня от настоящего прогноза погоды и текущего времени суток. Или, чтобы блоки опускались вниз.
3. Разные блоки имеют различные способности. Красные блоки являются нормальными. Зеленые блоки дают игроку возможность стрелять ракетами. Желтые блоки дают дополнительную жизнь. Стальные блоки невозможно разбить.
4. Каждый раз, когда игрок попадает ракетой по блоку или мяч уходит за экран, то экран сотрясает взрыв.
5. На нижней строке игры размещается несколько иконок, которые выполняют особые функции:
- если вы трижды нажмете на нижний левый угол, то включите хак-режим. В этом режиме вы можете пропустить данный уровень после того, как разобъете 4 блока.
- если вы нажмете на звуковую иконку, то выключите или включите звук игры.
- при нажатии на информационную иконку вы увидите подсказку с описанием правил игры.
- в правом нижнем углу экрана размещается иконка, обозначающая появление у вас ракет.
5. Игра содержит звуковые эффекты.

Пример экрана с подсказкой с описанием правил игры.

http://blog.roychowdhury.org/wp-content/uploads/2010/10/help.png

Обработка звуковых файлов перед вставкой в проект.
1. Загрузите WAV-файл в audacity
2. Откройте Project->New Audio Track. Это создаст пустой ауди-трек перед вашим моно-треком.
3. Кликните на старый трек, cmd+a, cmd+c (скопирует полный трек).
4. Кликниет на new audio track, cmd+v (вставит трек).
5. Кликните на "стрелку вниз" сверху слева старого аудио-трека (оядом с именем трека) и выберите "Make Stereo Track".
Экспортируйте получившееся в формат Wav.
В таком виде ваш файл сможет быть проигран на iPhone.

Какой способ создания графики будет использоваться?
iPhone SDK предлагает следующие фреймворки для  создания графики и анимции:
  1. UIKit
  2. Quartz 2D
  3. Open GL ES
Для нашей игры мы выберем UIKit и Quartz 2D.

Наша игра базируется на "view controller". Весь код будет располагаться только в файлах BricksViewController.h и BricksViewController.m. Остальные файлы - это wav-файлы для звуков, png-картинки для изображений и один mp3-файл для фоновой музыки, которая управляется кодом в файлах GBMusicTrack.h и GBMusicTrack.m.
Некоторая часть кода будет добавлена в файл BricksAppDelegate.m. Поскольку пользователь может выйти из приложения, то разумно в этом случае поставить игру на паузу. Для этого в файл BricksAppDelegate.m будут добавлены специальные методы.
Обратите внимание, что для iOS 3.x и 4.x эти методы будут различаться.

- (void)applicationWillTerminate:(UIApplication *)application {
    /*
     Called when the application is about to terminate.
     See also applicationDidEnterBackground:.
     */
    [viewController saveState];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
    /*
     Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
     If your application supports background execution, called instead of applicationWillTerminate: when the user quits.
     */
    [viewController saveState];
}
Метод applicationWillTerminate будет вызван для iOS 3.x, а метод applicationDidEnterBackground будет вызван для iOS 4.x. Все, что мы делаем это вызываем метод saveState в viewController. Обратите внимание, что эти методы могут вызываться только из файла делегата BricksAppDelegate, а не из файла BricksViewController.m. Это очень важно! Не сделайте ошибки.

Откройте файл BricksViewController.h. Скопируйте в него следующий код:

//
//  BricksViewController.h
//  Bricks
//
//  Created by Arjun on 10/15/10.
//  All rights reserved.
//
// Explosion sprites credit:
//
// Other tutorial credits:
// and Beginning iPhone Game Development by Apress
//
// Background images:
//
// Music:
// Jake Peterson's GBMusicTrack for background MP3 (classical music is "Minuet in G Major" by Bach from my personal collection)
#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioToolbox.h>
#import "GBMusicTrack.h"                // Using GBMusicTrack to play background MP3 (does NOT work in simulator)
#define BRICKS_ROWS 8
#define BRICKS_COLUMNS 7                // dimensions of the matrix of bris
#define BRICKS_IMAGES 5                 // I have 5 different brick images which can represent different powers
#define LIVES  8                        // Maximum lives you can have
#define GAME_LEVELS 4                   // Let's start with 4 levels
#define NORMAL_BRICK 1
#define METAL_BRICK  2                  // Metal bricks cannot be killed. You will bounce off sharply
#define SUPER_BRICKS 3                  // Any brick ID >=3 is a 'special' brick
#define LIFE_BRICK  3                   // This one gives you a new life (if you are not at max life)
#define MISSILE_BRICK 4                 // This one gives you 5 missiles with each hit
#define HACK_HITCOUNT 4                 // What's a game without a hack? If hack mode is on, you get to next level
                                        // after 4 hits
@interface BricksViewController : UIViewController <UIAccelerometerDelegate>
{
    NSTimer *gameTimer;                 // used for the primary game logic (update ball, check hits etc)
    NSTimer *backgroundTimer;           // used to animate backround sprites - cloud and missile movement
    NSTimer *harderTimer;               // after MAKE_IT_HARDER seconds, this will be called so we can step up the game difficulty
    IBOutlet UIImageView *ball;                 // will hold the ball as it bounces around
    IBOutlet UIImageView *paddle;               // will hold the paddle
    IBOutlet UIImageView *cloud;                // well, obviously, the cloud in the background
    IBOutlet UILabel *labelScore;               // holds the latest score shown on top right
    IBOutlet UILabel *labelGameOver;            // hidden by default, will showup in center as text when game is over
    IBOutlet UILabel *labelPause;               // text to display when paused
    IBOutlet UIImageView *explodeView;          // will hold explosion images
    IBOutlet UIImageView *backgroundImageView;  // holds the game background wallpaper
    IBOutlet UILabel *labelCurrentLevel;        // Text to display level on screen
    IBOutlet UIImageView *haveMissileView;      // Will show a missile at lower right when you have missiles
    IBOutlet UIImageView *hackView;             // Will show an icon at lower left when hack mode is on
    IBOutlet UIImageView *volumeView;           // swaps between volume on and off image - lower center
    IBOutlet UIImageView *infoView;             // displays the info button
    IBOutlet UIImageView *helpView;             // displays the help screen when you tap the info button
    // various sound events depending on what event occurs.
    SystemSoundID boingSound;
    SystemSoundID clapSound;
    SystemSoundID missSound;
    SystemSoundID hitSound;
    SystemSoundID explodeSound;
    SystemSoundID booSound;
    SystemSoundID holycowSound;
    SystemSoundID gotlifeSound;
    SystemSoundID missileSound;
    CGPoint ballSpeed;                                  // holds the speed at which the ball will move
    CGPoint paddleSpeed;                                // holds the speed at which the paddle will move
    int score;                                          // holds latest score - this is used by labelScore to display
    float touch;
    int livesleft;                                      // if this goes to 0, you are dead.
    int gameLevel;                                      // your current game level
    UIImageView *bricks[BRICKS_ROWS][BRICKS_COLUMNS];   // will hold the X*Y array of brick images
    int brick_description[BRICKS_ROWS][BRICKS_COLUMNS]; // will hold the brick format and powers
    UIImageView *lives[LIVES];                          // holds the display of lives on top left
    UIImageView *missileView;                           // hold the missile Image
    NSString *backgroundArray[GAME_LEVELS];             // holds different background images for levels
    NSString *brickImages[BRICKS_IMAGES];               // holds the different brick images
    NSMutableArray *explodeImages;                      // holds the sequence of images for an explosion
    NSMutableArray *rainDrops;                          // holds the rain drops that are displayed on the screen
    GBMusicTrack *song;                                 //background music
    int tBallSpeedX ;               // used temporarily for one bounce
    int tBallSpeedY ;               // used temporarily for one bounce
    int kBallSpeedX ;
    int kBallSpeedY ;
    int kMakeItHarder;              // Time to make things harder in the game
    int kGamePause;                 // if 1 then game freezes
    int kHackHitCount;              // HACK MODE: if 1, then we can skip levels just after 4 hits
    int kMissileFiring;             // 1 if a missile has launched. We only allow 1 missile at a time
    int kMissilesLeft;              // when 0, your missile power is done
    int kVolumeIsOn;                // toggles volume on and off
}
@property (nonatomic, retain) UIImageView *ball;
@property (nonatomic, retain) UIImageView *paddle;
@property (nonatomic, retain) UIImageView *cloud;
@property (nonatomic, retain) UIImageView *explodeView;
@property (nonatomic, retain) UIImageView *backgroundImageView;
@property (nonatomic, retain) UIImageView *missileView;
@property (nonatomic, retain) UIImageView *haveMissileView;
@property (nonatomic, retain) UIImageView *hackView;
@property (nonatomic, retain) UIImageView *volumeView;
@property (nonatomic, retain) UIImageView *infoView;
@property (nonatomic, retain) UIImageView *helpView;
@property (nonatomic, retain) UILabel *labelScore;
@property (nonatomic, retain) UILabel *labelCurrentLevel;
@property (nonatomic, retain) UILabel *labelGameOver;
@property (nonatomic, retain) UILabel *labelPause;
@property (nonatomic) SystemSoundID hitSound;
@property (nonatomic) SystemSoundID missSound;
@property (nonatomic) SystemSoundID clapSound;
@property (nonatomic) SystemSoundID explodeSound;
@property (nonatomic) SystemSoundID booSound;
@property (nonatomic) SystemSoundID holycowSound;
@property (nonatomic) SystemSoundID boingSound;
@property (nonatomic) SystemSoundID metalSound;
@property (nonatomic) SystemSoundID gotlifeSound;
@property (nonatomic) SystemSoundID missileSound;
@property (nonatomic, retain) NSTimer *gameTimer;
@property (nonatomic, retain) NSTimer *backgroundTimer;
@property (nonatomic, retain) NSTimer *harderTimer;
@property (nonatomic, retain) NSMutableArray *rainDrops;
- (void) initTimer;
- (void) playGame:(NSTimer *)timer;
- (void) adjustBackground:(NSTimer *)timer;
- (void) makeItHarder:(NSTimer *)timer;
- (void) initBricks;
- (void) initLives;
- (void) initRain;
- (void) moveRain;
- (void) hideRain;
- (void) initExplosion;
- (void) initAudioStreams: (SystemSoundID *)soundId: (NSString *)soundFile: (NSString *) type;
-(void) playAudio: (SystemSoundID) soundId;
- (void) resetAlphasforLives:(int) livesVal forBricks:(int) bricksVal ;
-(void) explodeBrick: (int) y : (int) x;
- (void) initBrickLayout;
- (void) launchMissile;
- (void) saveState;
@end
Теперь давайте его разберем.
Сперва импортируется файл AudioToolbox.h. Этот файл нужен для проигрывания аудио-файлов. Мы так же импортируем файл GBMusicTrack.h, который содержит класс для проигрывания фоновой музыки в формате MP3.
Далее следуют переменные с ключем  #define.

Матрица блоков, которую вы видите на каждом уровне представленаghjcnsv2D-массивом. Переменные RICKS_ROWS и BRICKS_COLUMNSопределяют его размеры. Хотя текущий код использует 4 изображения для разных супер-способностей, я зарезервировал 1 на будущее. Таки образом в коде их 5.
В программе используются различные значения для того, чтобы указать какой блок нормальный, а какой супер-блок. Я задаю 2D-матрицу и заполняю её значениями от 1 до 4 для того, чтобы когдя мяч или ракета попадает по блоку моя игра знала что делать: взорвать блок, дать игроку еще ракет, дать игроку дополнительную жизнь и так далее.
Наконец, я добавил "developer mode". Если игрок трижды нажмет на нижний левый угол экрана, то получите иконку "pac-man on fire", которая говорит, что вам не надо будет уничтожать все блоки для перехода на следующий уровень. Переменная HACK_HITCOUNTопределяет сколько блоков нужно будет сбить для перехода на следующий уровень.

#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioToolbox.h>
#import "GBMusicTrack.h"                // Using GBMusicTrack to play background MP3 (does NOT work in simulator)
#define BRICKS_ROWS 8
#define BRICKS_COLUMNS 7                // dimensions of the matrix of bricks
#define BRICKS_IMAGES 5                 // I have 5 different brick images which can represent different powers
#define LIVES  8                        // Maximum lives you can have
#define GAME_LEVELS 4                   // Let's start with 4 levels
#define NORMAL_BRICK 1
#define METAL_BRICK  2                  // Metal bricks cannot be killed. You will bounce off sharply
#define SUPER_BRICKS 3                  // Any brick ID >=3 is a 'special' brick
#define LIFE_BRICK  3                   // This one gives you a new life (if you are not at max life)
#define MISSILE_BRICK 4                 // This one gives you 5 missiles with each hit
#define HACK_HITCOUNT 4                 // What's a game without a hack? If hack mode is on, you get to next level
                                        // after 4 hits




Далее обратите внимание, что игровой класс унаследован от класса UIViewController, который был автоматически  добавлен Xcode, когда создавался проект View-based application. Но в дополнение к нему, для того, чтобы обнаружить движение акселерометра для перемещения ракетки по экрану, нужно добавить класс для акселерометра.
Далее мы определяем 3 таймера. Эти таймеры будут запускаться для определенных ситуаций. gameTimer будет срабатывать 30 раз в секунду для определения текущего состояния игры на экране: столкновений, ударов и так далее. backgroundTimer будет так же срабатывать 30 раз в секунду для создания фоновой анимации и движения ракет по экрану. harderTimer будет срабатывать один раз в 60 секунд для того чтобы поднять сложность уровня на 1 шаг, то есть каждые 60 секунд скорость движения мяча будет постоянно возрастать.
Далее мы определяем все переменные, относящиеся к UIImageView для управления различными объектами на экране. Для добавления на экран картинок используется Interface Builder, за исключением картинок самих блоков, которые будут добавляться на экран динамически через код построения уровня. Все остальное будет создано в Interface Builder.
@interface BricksViewController : UIViewController <UIAccelerometerDelegate>
{
    NSTimer *gameTimer;                 // used for the primary game logic (update ball, check hits etc)
    NSTimer *backgroundTimer;           // used to animate backround sprites - cloud and missile movement
    NSTimer *harderTimer;               // after MAKE_IT_HARDER seconds, this will be called so we can step up the game difficulty
    IBOutlet UIImageView *ball;                 // will hold the ball as it bounces around
    IBOutlet UIImageView *paddle;               // will hold the paddle
    IBOutlet UIImageView *cloud;                // well, obviously, the cloud in the background
    IBOutlet UILabel *labelScore;               // holds the latest score shown on top right
    IBOutlet UILabel *labelGameOver;            // hidden by default, will showup in center as text when game is over
    IBOutlet UILabel *labelPause;               // text to display when paused
    IBOutlet UIImageView *explodeView;          // will hold explosion images
    IBOutlet UIImageView *backgroundImageView;  // holds the game background wallpaper
    IBOutlet UILabel *labelCurrentLevel;        // Text to display level on screen
    IBOutlet UIImageView *haveMissileView;      // Will show a missile at lower right when you have missiles
    IBOutlet UIImageView *hackView;             // Will show an icon at lower left when hack mode is on
    IBOutlet UIImageView *volumeView;           // swaps between volume on and off image - lower center
    IBOutlet UIImageView *infoView;             // displays the info button
    IBOutlet UIImageView *helpView;             // displays the help screen when you tap the info button

Далее мы определяем различные переменные для звуковых файлов. Каждый звук является WAV-файлом.


boing - когда мяч отскакивает от границ экрана.
clap - когда вы успешно завершаете уровень.
miss - когда ракетка пропускает мяч и он уходит за кран.
hit - когда мяч попадает по ракетке.
explode - когда ракета или мяч попадают по блоку.
boo - когда у игрока заканчиваются все жизни и он проигрывает.
holycow -  когда возрастает сложность игры (harderTimer) и сокрость движения мяча на уровне ускоряется.
gotlife - когда мяч попадает по блоку, который дает игроку дополнительную жизнь.
missile - когда игрок запускает ракету.

// various sound events depending on what event occurs.
    SystemSoundID boingSound;
    SystemSoundID clapSound;
    SystemSoundID missSound;
    SystemSoundID hitSound;
    SystemSoundID explodeSound;
    SystemSoundID booSound;
    SystemSoundID holycowSound;
    SystemSoundID gotlifeSound;
    SystemSoundID missileSound;


Переменная paddleSpeed контролирует скорость движения мяча. Зависит от того, обо что ударился мяч. Её X и Y координаты будут изменены и позже мяч будет двигаться относительно его текущего положени, базируясь на значениях X, Y и paddleSpeed.


CGPoint ballSpeed;                                  // holds the speed at which the ball will move
    CGPoint paddleSpeed;                                // holds the speed at which the paddle will move
    int score;                                          // holds latest score - this is used by labelScore to display
    float touch;
    int livesleft;                                      // if this goes to 0, you are dead.
    int gameLevel;                                      // your current game level
2 массива являются серцем того, как блоки отображаются и контролируются на экране.
Блоки - это 2D массив, который содержит изображения блоков для каждого уровня. Это массив UIImageView. Каждый блок - это слой ImageView.
brick_description - это 2D массив того же размера, который содержит  значения, определяющие особенность блоков "brick power".
Для отрисовки блоков на экране будет использоваться вложенный циклы. Для каждой строки/колонки мы будем считывать brick_description_value (no brick, normal brick, steel brick, life brick, missile brick) и затем отображать соответствующую картинку. Изменяя массив вы сможете менять дизайн уровня.
UIImageView *bricks[BRICKS_ROWS][BRICKS_COLUMNS];   // will hold the X*Y array of brick images
int brick_description[BRICKS_ROWS][BRICKS_COLUMNS]; // will hold the brick format and powers
Поскольку мы используем разные фоновые картинки для каждого уровня, то они сохраняются в массиве backgroundArray, который содержит имена этих картинок.
Массив explodeImages содержит 35 картинки-спрайта для анимации взрыва, когда мяч ударяет по блоку.
Массив rainDrops содержит 50 изображений капель дождя, для создания анимации дождя на некоторых уровнях.
song - содержит указатель на mp3 файл фоновой музыки.
tBallSpeedX и Y - переменные, которые заполняются, когда вы ударяете по стальному блоку. Когда вы рассчитываете следующее положение мяча, вы берете текущее его положение + скорость мяча + tBallSpeed (position+ball speed+tBallSpeed). Это сделано для того, чтобы мяч стал двигаться быстрее после того, как ударит по стальному блоку.
Если мяч ударяется о границу экрана мы делаем tBallSpeedX и Y равными 0.
Проще говоря, мы ускоряем мяч после удара о стальной блок и замедляем его после удара о границу экрана.
Переменная kMakeItHarder содержит время в секундах после которого скорость мяча будет увеличена. Другими словами harderTimer запускается позже на kMakeItHarder секунд.
В переменную kMissileFiring будет установлено 1, когда игрок запустит ракету. Пока она содержит 1  игрок не сможет запустить вторую ракету. Когда ракета попадет в блок или уйдет за границу экрана, тогда значение переменной kMissileFiring будет очищено и игрок сможет запустить еще ракету.
Переменная kMissilesLeft содержит число имеющихся у игрока ракет. Когда вы попадает в блок, то получаете 5 ракет. Когда вы выстреливаете ракету, то их число уменьшается в переменной kMissilesLeft на 1.

UIImageView *lives[LIVES];                          // holds the display of lives on top left
    UIImageView *missileView;                           // hold the missile Image
    NSString *backgroundArray[GAME_LEVELS];             // holds different background images for levels
    NSString *brickImages[BRICKS_IMAGES];               // holds the different brick images
    NSMutableArray *explodeImages;                      // holds the sequence of images for an explosion
    NSMutableArray *rainDrops;                          // holds the rain drops that are displayed on the screen
    GBMusicTrack *song;                                 //background music
    int tBallSpeedX ;               // used temporarily for one bounce
    int tBallSpeedY ;               // used temporarily for one bounce
    int kBallSpeedX ;
    int kBallSpeedY ;
    int kMakeItHarder;              // Time to make things harder in the game
    int kGamePause;                 // if 1 then game freezes
    int kHackHitCount;              // HACK MODE: if 1, then we can skip levels just after 4 hits
    int kMissileFiring;             // 1 if a missile has launched. We only allow 1 missile at a time
    int kMissilesLeft;              // when 0, your missile power is done
    int kVolumeIsOn;                // toggles volume on and off
}
Далее идет стандартный код синтезирования перемнных. Просто скопируйте его к себе в проект.
@property (nonatomic, retain) UIImageView *ball;
@property (nonatomic, retain) UIImageView *paddle;
@property (nonatomic, retain) UIImageView *cloud;
@property (nonatomic, retain) UIImageView *explodeView;
@property (nonatomic, retain) UIImageView *backgroundImageView;
@property (nonatomic, retain) UIImageView *missileView;
@property (nonatomic, retain) UIImageView *haveMissileView;
@property (nonatomic, retain) UIImageView *hackView;
@property (nonatomic, retain) UIImageView *volumeView;
@property (nonatomic, retain) UIImageView *infoView;
@property (nonatomic, retain) UIImageView *helpView;
@property (nonatomic, retain) UILabel *labelScore;
@property (nonatomic, retain) UILabel *labelCurrentLevel;
@property (nonatomic, retain) UILabel *labelGameOver;
@property (nonatomic, retain) UILabel *labelPause;
@property (nonatomic) SystemSoundID hitSound;
@property (nonatomic) SystemSoundID missSound;
@property (nonatomic) SystemSoundID clapSound;
@property (nonatomic) SystemSoundID explodeSound;
@property (nonatomic) SystemSoundID booSound;
@property (nonatomic) SystemSoundID holycowSound;
@property (nonatomic) SystemSoundID boingSound;
@property (nonatomic) SystemSoundID metalSound;
@property (nonatomic) SystemSoundID gotlifeSound;
@property (nonatomic) SystemSoundID missileSound;
@property (nonatomic, retain) NSTimer *gameTimer;
@property (nonatomic, retain) NSTimer *backgroundTimer;
@property (nonatomic, retain) NSTimer *harderTimer;
@property (nonatomic, retain) NSMutableArray *rainDrops;
 
Наконец мы декларируем некоторые внутренние методы нашего игрового класса, которые будут объяснены ниже в файле @ implementation.
- (void) initTimer;
- (void) playGame:(NSTimer *)timer;
- (void) adjustBackground:(NSTimer *)timer;
- (void) makeItHarder:(NSTimer *)timer;
- (void) initBricks;
- (void) initLives;
- (void) initRain;
- (void) moveRain;
- (void) hideRain;
- (void) initExplosion;
- (void) initAudioStreams: (SystemSoundID *)soundId: (NSString *)soundFile: (NSString *) type;
-(void) playAudio: (SystemSoundID) soundId;
- (void) resetAlphasforLives:(int) livesVal forBricks:(int) bricksVal ;
-(void) explodeBrick: (int) y : (int) x;
- (void) initBrickLayout;
- (void) launchMissile;
- (void) saveState;
Теперь перейдем к файлу BricksViewController.m.

//
//  BricksViewController.m
//  Bricks
//
//  Created by Arjun on 10/15/10.
//  All rights reserved.
//
#import "BricksViewController.h"
#import "GBMusicTrack.h"
@implementation BricksViewController
@synthesize ball;
@synthesize paddle;
@synthesize cloud;
@synthesize labelScore;
@synthesize labelCurrentLevel;
@synthesize labelPause;
@synthesize labelGameOver;
@synthesize explodeView;
@synthesize backgroundImageView;
@synthesize missileView;
@synthesize haveMissileView;
@synthesize hackView;
@synthesize volumeView;
@synthesize infoView;
@synthesize helpView;
@synthesize hitSound;
@synthesize missSound;
@synthesize explodeSound;
@synthesize clapSound;
@synthesize boingSound;
@synthesize booSound;
@synthesize holycowSound;
@synthesize metalSound;
@synthesize gotlifeSound;
@synthesize missileSound;
@synthesize gameTimer;
@synthesize backgroundTimer;
@synthesize harderTimer;
@synthesize rainDrops;
//=============================================================================
- (void)viewDidLoad {
    NSLog(@"Inside ViewDidLoad");
    [super viewDidLoad];
    //************************
    kHackHitCount=0;                // 1=skip levels quickly. 0=normal play
    //************************
    kBallSpeedX = 4;
    kBallSpeedY = 4;
    tBallSpeedX = 0;
    tBallSpeedY = 0;
    kMakeItHarder = 10;             // Time in seconds to make things harder in the game
    kGamePause = 1;
    gameTimer = nil;
    backgroundTimer = nil;
    harderTimer = nil;
    gameLevel =1;
    kMissileFiring=0;
    kMissilesLeft=0;
    kVolumeIsOn=1;
    kMakeItHarder = 60;             // Time in seconds to make things harder in the game
    backgroundArray[0]=@"level1.png";
    backgroundArray[1]=@"level2.png";
    backgroundArray[2]=@"level3.png";
    backgroundArray[3]=@"level4.png";
    [backgroundImageView setImage:[UIImage imageNamed: backgroundArray[gameLevel-1]]];
    // set up the accelerometer - I want to use it to move my paddle
    UIAccelerometer *myAccel = [UIAccelerometer sharedAccelerometer];
    myAccel.updateInterval = 1.0f/30.0f;
    myAccel.delegate=self;
    ballSpeed = CGPointMake(kBallSpeedX,kBallSpeedY);
    score = 0;
    livesleft = LIVES;
    // Initialize all the audio streams that we will be playing
    UInt32 sessionCategory = kAudioSessionCategory_AmbientSound;
    AudioSessionSetProperty (kAudioSessionProperty_AudioCategory,
                             sizeof (sessionCategory),
                             &sessionCategory);
    [self initAudioStreams:&hitSound :@"hitme" :@"wav"];
    [self initAudioStreams:&booSound :@"boo" :@"wav"];
    [self initAudioStreams:&missSound :@"die" :@"wav"];
    [self initAudioStreams:&clapSound :@"applause" :@"wav"];
    [self initAudioStreams:&explodeSound :@"explode" :@"wav"];
    [self initAudioStreams:&boingSound :@"boing" :@"wav"];
    [self initAudioStreams:&holycowSound :@"holycow" :@"wav"];
    [self initAudioStreams:&metalSound :@"metalsound" :@"wav"];
    [self initAudioStreams:&gotlifeSound :@"gotlife" :@"wav"];
    [self initAudioStreams:&missileSound :@"missile" :@"wav"];
    // I am blindly using GBMusicTrack for now based on their instructions...
    song = [[GBMusicTrack alloc] initWithPath:[[NSBundle mainBundle] pathForResource:@"background" ofType:@"mp3"]];
    //[song setRepeat:YES];
    //[song play];
    [self initBricks];      // fill in and display all the bricks
    [self initLives];
    [self initExplosion];   // pre-load explosion array
    [self initRain];        // pre-load rain array
    [self initTimer];       // start game timers and we are on our way!
    UIImage *image = [UIImage imageNamed:@"missile.png"];
    missileView = [[[UIImageView alloc] initWithImage:image] autorelease];
    missileView.hidden=YES;     // load the missile image, hide it for now
    [self.view addSubview:missileView];
    ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);
}
//=============================================================================
// play sounds only if volume is not muted
-(void) playAudio: (SystemSoundID) soundId
{
    if (kVolumeIsOn) {AudioServicesPlaySystemSound(soundId);}
}
//=============================================================================
// convenience function. Pass it a reference to the SystemSoundID handle, the file and its type and it
// creates an audio stream ready to play
- (void) initAudioStreams: (SystemSoundID *)soundId :(NSString *)soundFile  :(NSString *) type
{
    NSString *audioPath=[[NSBundle mainBundle] pathForResource:soundFile ofType:type];
    CFURLRef audioURL = (CFURLRef) [NSURL fileURLWithPath:audioPath];
    AudioServicesCreateSystemSoundID(audioURL, soundId);
}
//=============================================================================
// pre-load the explosion effect into an array.
- (void) initExplosion
{
    explodeImages = [[NSMutableArray alloc] initWithCapacity: 34];
    for (int i = 1; i <= 34; i++)
    {
        NSString *imageName = [NSString stringWithFormat:@"boom%d.png",i];
        UIImage *image = [UIImage imageNamed:imageName ];
        [explodeImages addObject: image];
    }
}
//=============================================================================
// load bricks layout. This array controls what sort of bricks show at each
// cell of the brick matrix
- (void) initBrickLayout
{
    NSLog(@"init brick layout");
    NSLog(@"Current Game Level is %d",gameLevel);
    // You can modify these arrays to respresent the bricks
    // as you want for each level. Each block is for a particular level.
    // 0 = no brick at this cell
    // 1 = normal brick - you hit it, it explodes and you add to your score
    // 2 = metal brick - you can't kill it - the ball will hit and bounce off faster
    // 3 = super brick - you hit it, it explodes and you will get a new life (if you are not at max)
    // 4 = missile brick - you get 5 missiles each time you hit this one
    int bricklevel[GAME_LEVELS][BRICKS_ROWS][BRICKS_COLUMNS] = {
    {
            // level 1
            {2,0,0,1,0,0,2},
            {0,0,1,1,1,0,0},
            {0,1,4,0,4,1,0},
            {1,1,3,0,3,1,1},
            {1,1,1,0,1,1,1},
            {0,1,3,0,3,1,0},
            {0,0,4,1,4,0,0},
            {2,0,0,1,0,0,2},
    },
    {
            // level 2
            {3,2,1,1,1,2,3},
            {1,1,1,1,1,1,1},
            {2,1,1,1,1,1,2},
            {1,1,1,1,1,1,1},
            {1,1,2,3,2,1,1},
            {4,1,1,2,1,1,4},
            {1,2,1,1,1,2,1},
            {2,1,1,1,1,1,2},
    },
    {
            // level 3
            {0,0,0,3,0,0,0},
            {0,0,1,1,1,0,0},
            {0,0,1,1,1,0,0},
            {0,2,1,1,1,2,0},
            {0,1,1,1,1,1,0},
            {2,1,1,1,1,1,2},
            {1,4,1,1,1,4,1},
            {1,1,2,1,2,1,1},
    },
    {
            // level 4
            {0,1,0,3,0,1,0},
            {2,1,1,1,1,4,2},
            {0,1,1,1,1,1,0},
            {0,0,1,1,1,0,0},
            {0,0,1,1,1,0,0},
            {0,1,1,1,1,1,0},
            {2,4,1,3,1,1,2},
            {1,0,1,0,1,0,1},
    },
    };
    NSLog (@"Copying level %d(-1) to bricks description", gameLevel);
    memcpy (brick_description, bricklevel[gameLevel-1], sizeof(brick_description)); // C still rocks. W00t :-)
}
//=============================================================================
// init rain array
- (void) initRain
{
    NSLog(@"Inside initRain");
    NSMutableArray *array = [[NSMutableArray alloc] init];
    self.rainDrops = array;
    [array release];
    UIImage *rainImage = [UIImage imageNamed:@"raindrop.png"];
    UIImageView *rainView;
    // randomly place 50 rain drops across the screen
    for (int i = 0; i< 50; i++)
    {
        rainView= [[UIImageView alloc] initWithImage:rainImage];
        rainView.alpha=0.3;
        rainView.hidden=YES;
        int x = arc4random()% (int)self.view.bounds.size.width;
        int y = arc4random()% (int)self.view.bounds.size.height;
        rainView.center = CGPointMake (x,y);
        // the background image is at index 0, make rain at 1,
        // so it is behind the bricks
        [self.view insertSubview:rainView atIndex:1];
        [self.rainDrops addObject: rainView];
        [rainView release];
    }
}
// we need to hide the rain for layers that don't have rain
- (void) hideRain
{
    for (UIImageView *rain in rainDrops)
    {
        rain.hidden=YES;
    }
}
// animate the rain across the screen
-(void)moveRain{
    for (UIImageView *rain in rainDrops) {
        CGPoint newCenter = rain.center;
        newCenter.y = newCenter.y +2;
        newCenter.x = newCenter.x +1;
        rain.hidden = NO;
        if (newCenter.y > self.view.bounds.size.height){
            newCenter.y=0;
            newCenter.x = arc4random()%(int)self.view.bounds.size.width;
        }
        if (newCenter.x > self.view.bounds.size.width){
            newCenter.x =0;
        } else if (newCenter.x < 0) {
            newCenter.x = self.view.bounds.size.width;
        }
        rain.center = newCenter;
    }
}
//=============================================================================
// launches a missile
// you would think we can use Core Graphics animate to automatically do this
// but we can't as we also need to detect if it hit a brick with CGRectIntersect
// The automatic animation does not allow for detection if intersects, so we need
// to do it ourselves, or, use auto animation in small steps.
- (void) launchMissile;
{
    if (!kMissilesLeft) {NSLog(@"You don't have missiles");return;}
    if (kMissileFiring) {return;} // only allow 1 missile at a time
    haveMissileView.hidden=NO;
    kMissileFiring=1;
    missileView.hidden=NO;
    [self playAudio:missileSound];
    missileView.center=CGPointMake(paddle.center.x, paddle.center.y-10);
    kMissilesLeft--;
    if (!kMissilesLeft) {haveMissileView.hidden=YES;}
    NSLog(@"You have %d missiles left",kMissilesLeft);
}
-(void) missileFinished
{
    missileView.hidden=YES;
    kMissileFiring=0;
}
//=============================================================================
// load the bricks images and create the bricks matrix. Also create the lives display
- (void) initBricks
{
    NSLog(@"Inside initBricks");
    brickImages[0]=@"brick2.png"; //normal
    brickImages[1]=@"brick2.png";
    brickImages[2]=@"steel.png"// cant destroy
    brickImages[3]=@"brick3.png"; // special powers - life
    brickImages[4]=@"brick1.png"; // special powers - missiles
    [self initBrickLayout];
    for (int rows=0; rows self.view.bounds.size.width+80) {newposx=-64;}    // wrap it around
        cloud.center = CGPointMake(newposx, cloud.center.y);
    }
    else
    {
        cloud.hidden=YES;
        [self moveRain];
    }
    if (kMissileFiring)
    {
        int missileY;
        missileY=missileView.center.y-10;
        if (missileY<0)
        {
            [self missileFinished];
        }
        else
        {
            missileView.center=CGPointMake(missileView.center.x, missileY);
        }
    } // kMissileFiring
}
//=============================================================================
// resets game back to normal.
-(void) resetGame
{
    NSLog(@"Inside reset Game");
    score = 0;
    livesleft = LIVES;
    kBallSpeedX = 4;
    kBallSpeedY = 4;
    ballSpeed = CGPointMake(kBallSpeedX,kBallSpeedY);
    gameLevel = 1;
    labelCurrentLevel.text = [NSString stringWithFormat:@"%d",gameLevel];
    labelScore.text = [NSString stringWithFormat:@"%d", score];
    kMissileFiring=0;
    kMissilesLeft=0;
    haveMissileView.hidden=YES;
    for (UIImageView *rain in rainDrops) { rain.hidden = YES; }
    [backgroundImageView setImage:[UIImage imageNamed: backgroundArray[gameLevel-1]]];
    NSLog(@"Calling initBricks with level:%d",gameLevel);
    [self remapBricks];
    [self initTimer];
    kGamePause=1;
    ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);
    NSLog(@"*****GAME RESET***********");
}
//=============================================================================
// This is the heart of the game. Checks for collisions, hits, misses etc. Can be cleaned up better
- (void) playGame:(NSTimer *)timer
{
    static int hack_hit_count = 0;
    if (kGamePause) {return;}
    //First Check for brick collision
    // basically, we iterate through the array of bricks
    // and compare if the ball location intersects with any of the bricks location
    // From a display perspective, we are not really deleting the brick. We are simply making its alpha (transparency)
    // to 0 so it looks like its gone. Therefore, when we check if the ball has hit a brick, we also need to check if
    // the brick is not transparent. If it is, its been hit before.
#pragma mark Brick Collision logic
    int youWin=1;      // if this is 0, then there is atleast one brick that has not been hit. If 1, all bricks
                       // have been hit.
    for (int y=0; y=NORMAL_BRICK))
            {
                if (brick_description[y][x] !=METAL_BRICK) {youWin=0;}
                 if (ballHitBrick || (missileHitBrick) && (missileView.hidden==NO))
                 {
                    // Coming here means that the ball has hit a brick that has not been hit before
                     // or, the missile hit a brick
                    if ((brick_description[y][x]==NORMAL_BRICK) || (brick_description[y][x]>=SUPER_BRICKS))
                    {
                        NSLog(@"YOU HIT:%d:%d",y,x);
                        if (kHackHitCount) {hack_hit_count++;}
                        //NSLog (@"HACK HIT COUNT %d & kHack:%d", hack_hit_count, kHackHitCount);
                        score += 10;
                        labelScore.text = [NSString stringWithFormat:@"%d", score];
                        if (missileHitBrick)
                        {
                            NSLog(@"MISSILE HIT");
                            missileView.hidden=YES; // after a hit, hide the missile
                            kMissileFiring=0;   // allow other missiles to be fired
                        }
                        [self playAudio:explodeSound];
                        [self explodeBrick :y :x];
                        bricks[y][x].alpha -= 0.1; // reduce alpha right away by a pt.
                                                  // just in case you hit it again before anim begins....
                        [UIView beginAnimations:@"fadebrick" context:nil];
                        [UIView setAnimationCurve:UIViewAnimationCurveLinear];
                        [UIView setAnimationDuration:0.8];
                        [UIView setAnimationDelegate:self];
                        bricks[y][x].alpha=0;
                        [UIView commitAnimations]; // fade out killed brick
                        if (brick_description[y][x]==LIFE_BRICK) // this means you get a life
                        {
                            if (livesleft0) && (ballSpeed.y>0)) { ballSpeed.y = -ballSpeed.y;}
                        else if ((ballSpeed.x>0) && (ballSpeed.y<0)) { ballSpeed.y = -ballSpeed.y;}
                        else if ((ballSpeed.x<0) && (ballSpeed.y>0)) { ballSpeed.x = -ballSpeed.x;}
                        else if ((ballSpeed.x<0) && (ballSpeed.y<0)) { ballSpeed.y = -ballSpeed.y;}
                        if (ballSpeed.x <0) tBallSpeedX = -tBallSpeedX;
                        if (ballSpeed.y <0) tBallSpeedY = -tBallSpeedY;
                    }
                } // CGRectIntersect
            } // if you hit a solid brick
        } //x
    } //y
#pragma mark Winning Logic
    /*********** You've won **********************/
    if ((kHackHitCount) && (hack_hit_count>=HACK_HITCOUNT))
    {
        hack_hit_count=0;
        youWin=1;
        NSLog(@"**Hack hitcount reached");
    }
    if (youWin) // all bricks over
    {
        NSLog(@"Inside youWin");
        [self resetAlphasforLives:-1 forBricks:0]; // remove all bricks, don't touch lives
        [self playAudio:clapSound];
        if (gameLevel == GAME_LEVELS)
        {
            NSLog(@"*************Inside game over***************");
            labelGameOver.text = @"You Rock!";
            labelGameOver.hidden=NO;                         // this will display the Game Over message
            [gameTimer invalidate]; gameTimer=nil;              // kill all timers - game is over
            [backgroundTimer invalidate]; backgroundTimer=nil;
            [harderTimer invalidate]; harderTimer=nil;
            return;
        }
        else {
            NSLog(@"Inside move to next nevel");
            labelGameOver.text = [NSString stringWithFormat:@"Level %d", gameLevel+1];
            labelGameOver.hidden=NO;
            [gameTimer invalidate]; gameTimer=nil;
            [backgroundTimer invalidate]; backgroundTimer=nil;
            [harderTimer invalidate]; harderTimer=nil;
            return;
        }
    }
#pragma mark Paddle hits ball logic
    // Now lets check if the paddle hit the ball
    if (CGRectIntersectsRect(ball.frame, paddle.frame) && ball.center.y=20)
        {
            NSLog(@"Edge Hit Detected");
            int paddleCenterWidth = paddle.frame.size.width/2;
            if (    (paddle.center.x+paddleCenterWidth < self.view.bounds.size.width-10)
                 && (paddle.center.x - paddleCenterWidth > 10))
            {
                ballSpeed.x = (edge<0)? -kBallSpeedX:kBallSpeedX;
                NSLog(@"Edge skew activated:Ball Speed set to %f",ballSpeed.x);
            }
        }
        tBallSpeedX = 0; tBallSpeedY =0;
        labelScore.text = [NSString stringWithFormat:@"%d", score];
    }
#pragma mark Ball bouncing off screen check
    // now check if the ball is going beyond screen boundaries, if so,
    // bring it back
    if (ball.center.x+ballSpeed.x>self.view.bounds.size.width-4 || ball.center.x+ballSpeed.x-4<0)
    {[self playAudio: boingSound]; ballSpeed.x = -ballSpeed.x; tBallSpeedX=0; tBallSpeedY=0;}
    if (ball.center.y+ballSpeed.y-4 >self.view.bounds.size.height || ball.center.y+ballSpeed.y-4 <0)
        {ballSpeed.y = -ballSpeed.y;[self playAudio: boingSound]; tBallSpeedX=0; tBallSpeedY=0;}
    int x,y;
    x= ball.center.x+ballSpeed.x+tBallSpeedX;
    y = ball.center.y+ballSpeed.y+tBallSpeedY;
    if (x<0) { x = 0;}
    if (y<0) { y = 0;}
    // catchall
    if (x>self.view.bounds.size.width-4) { x=self.view.bounds.size.width-4;}
    if (y>self.view.bounds.size.height-4) { y=self.view.bounds.size.height-4;}
    ball.center = CGPointMake(x, y);
#pragma mark Paddle missing the ball logic
    // When you miss the ball, we want the ball to fall off the screen, not just disappear immediately
    // when your paddle misses. So we set this youmissed flag to 1 when the paddle misses and the ball keeps going down
    // till it reaches the edge of the screen. When it does, we decrease the score, kill a life and play sound
    static int youmissed=0;
    if ((ball.center.y > paddle.center.y) && (youmissed==0))
    {
        youmissed=1;
        labelScore.text = [NSString stringWithFormat:@"%d", score];
    }
    if (ball.center.y >= self.view.bounds.size.height-4) // have we run off screen?
    {
        score -= 10;
        labelScore.text = [NSString stringWithFormat:@"%d", score];
        --livesleft;
        // if you lose a life, lets rotate the life icon around and then make it disappear
        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDuration:0.2];
        [UIView setAnimationCurve:UIViewAnimationCurveLinear];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(lifeDeathAnimeDidStop)];
        CGAffineTransform transform1 = CGAffineTransformMakeScale(1,1);
        CGAffineTransform transform2 = CGAffineTransformMakeRotation(179.9 * M_PI /180.0);
        lives[livesleft].transform = CGAffineTransformIdentity;
        lives[livesleft].transform = CGAffineTransformConcat(transform1, transform2);
        [UIView commitAnimations];
        // After you die, set ball back to center
        ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);
        // and make sure the ball goes down
        ballSpeed.y = kBallSpeedY;
        [self playAudio:missSound];
        youmissed=0;
        kGamePause = 1; // lets freeze the game till he taps
        labelPause.hidden = NO;
#pragma mark You lost logic
        /*********** You've lost **********************/
        if (livesleft == 0)
        {
            NSLog(@"inside no lives left");
            // all lives over, so pop the message out and stop timers
            // game is frozen
            labelGameOver.text = @"Bad Luck!";
            labelGameOver.hidden=NO;
            labelPause.hidden=YES;
            [gameTimer invalidate]; gameTimer = nil;
            [backgroundTimer invalidate]; backgroundTimer = nil;
            [harderTimer invalidate]; harderTimer = nil;
            [self playAudio:booSound];
        } // livesleft
    } // if ball.center.y
} // playgame
// called after death rotation animation completes
- (void)lifeDeathAnimeDidStop{
    if ((livesleft=0)) lives[livesleft].alpha = 0;
}
//=============================================================================
// used to detect any touches on the screen and trigger different operations
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // I am not forcing you to touch the paddle. You can touch and drag anywhere to move the paddle
    // example, its easier to drag below the paddle that way you see the paddle as well
    UITouch *mytouch = [[event allTouches] anyObject];
    touch = paddle.center.x - [mytouch locationInView:mytouch.view].x;
    NSArray *allTouches = [touches allObjects];
    // Did you tap on the info view ?
    if (CGRectContainsPoint(infoView.frame, [[allTouches objectAtIndex:0] locationInView:self.view]))
    {
        if (helpView.hidden=YES)
        {
            helpView.hidden=NO;
            [self.view bringSubviewToFront:helpView];
            kGamePause=1; // pause the game when you show help
            return;
        }
    }
    // did you tap on the help screen after it was displayed?
    if ((CGRectContainsPoint(helpView.frame, [[allTouches objectAtIndex:0] locationInView:self.view]) &&(helpView.hidden==NO)))
    {
        helpView.hidden=YES;
        return;
    }
    // did you tap on the volume icon?
    if (CGRectContainsPoint(volumeView.frame, [[allTouches objectAtIndex:0] locationInView:self.view]))
    {
        NSLog(@"Volume tapped");
        kVolumeIsOn ^=1;
        // toggle the volume image and status on and off with each tap
        if (kVolumeIsOn)
        {
            [volumeView setImage:[UIImage imageNamed: @"volumeon.png"]];
        }
        else {
            [volumeView setImage:[UIImage imageNamed: @"volumeoff.png"]];
        }
        return;
    }
    if (labelGameOver.hidden == NO) // game was over so restart if user touches anywhere
    {
        if ((gameLevel == GAME_LEVELS) || (livesleft == 0) )
        // game is over, so restart from level 1
        {
            NSLog(@"After touch: Game is over, reset");
            labelGameOver.hidden = YES;
            // reset all parameters of the game and lets go again
            [self resetAlphasforLives:1 forBricks:-1];
            [self resetGame];
            ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);
            // make the ball go down
            if (ballSpeed.y < 0) ballSpeed.y = -ballSpeed.y;
            labelScore.text = [NSString stringWithFormat:@"%d", score];
        // we need to restart the timers, because we killed them when the game got over
        //[self initTimer];
        }
        else // go to next level
        {
            NSLog(@"inside touchesBegan:next level");
            gameLevel++;
            [backgroundImageView setImage:[UIImage imageNamed: backgroundArray[gameLevel-1]]];
            labelCurrentLevel.text = [NSString stringWithFormat:@"%d",gameLevel];
            labelGameOver.hidden=YES;
            ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);
            [self initTimer];
            [self remapBricks];
        }
    }
    if (kGamePause) // if so, then I need to resume with touch
    {
        [harderTimer invalidate]; // if you paused, don't keep the timer for making it harder alive.
        kGamePause = 0;
        labelPause.hidden=YES;
        [self.view bringSubviewToFront:helpView];
        harderTimer = [NSTimer scheduledTimerWithTimeInterval:kMakeItHarder target:self selector:@selector(makeItHarder:) userInfo:nil repeats:YES];
    }
    else
    {
        // double tapping shoots a missile
        if ([mytouch tapCount] == 2)
        {
            NSLog(@"Tap Twice - Missile Launch");
            [self launchMissile];
        }
        // triple tapping in the lower left part of the screen toggles 'hack mode'
        else if (([mytouch tapCount]==3) &&
                 (CGRectContainsPoint(hackView.frame, [[allTouches objectAtIndex:0] locationInView:self.view])))
        {
            NSLog(@"Tap thrice - HACK MODE!!");
            kHackHitCount ^=1;
            if (kHackHitCount) {hackView.hidden=NO;} else {hackView.hidden=YES;}
        } // tapcount=3
    } // not paused
}
// this is called when I drag my finger after first touching the screen
// this will move the paddle
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *mytouch = [[event allTouches] anyObject];
    float distance = ([mytouch locationInView:mytouch.view].x + touch) - paddle.center.x;
    float newx = paddle.center.x + distance;
    // we want to make sure the paddle is not dragged off screen, so limit its movement to a point
    // where the full paddle is visible when dragged
    int paddleCenterWidth = paddle.frame.size.width/2;
    if (newx < paddleCenterWidth) {newx=paddleCenterWidth;}
    if (newx > self.view.bounds.size.width-paddleCenterWidth) {newx=self.view.bounds.size.width-paddleCenterWidth;}
    paddle.center = CGPointMake(newx, paddle.center.y);
}
/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/
// Another way to move the paddle. Just rotate the phone...
- (void) accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)accel
{
    float newPaddleX= paddle.center.x + (accel.x *12);
    int paddleCenterWidth = paddle.frame.size.width/2;
    if (newPaddleX > paddleCenterWidth && newPaddleX < self.view.bounds.size.width-paddleCenterWidth)
        paddle.center=CGPointMake(newPaddleX, paddle.center.y);
}
// This is called when the ball hits a solid brick. It shows an explosion
-(void) explodeBrick: (int) y : (int) x
{
    NSLog(@"Explosion!!!!!");
    // Position the explosion so it is properly centered on the brick
    explodeView.center = CGPointMake(bricks[y][x].center.x, bricks[y][x].center.y);
    // bring the explosion view to the front
    [self.view bringSubviewToFront:explodeView];
    explodeView.animationImages = explodeImages;
    explodeView.animationDuration = 1;
    explodeView.animationRepeatCount = 1;
    [explodeView startAnimating];
}
// resets alphas of images of the game. This is just a convenience function
// that I can call from different places to display or hide the lives left and
// the bricks opacity
- (void) resetAlphasforLives:(int) livesVal forBricks:(int) bricksVal
{
    NSLog(@"Inside resetAlphaseforLives");
    if (bricksVal != -1)
    {
        for (int rows=0; rows<BRICKS_ROWS; rows++)
        {
            for (int columns=0; columns<BRICKS_COLUMNS; columns++)
            {
                CGRect myFrame = bricks[rows][columns].frame;
                //myFrame.size.width=60;
                //myFrame.size.height=16;
                bricks[rows][columns].frame= myFrame;
                bricks[rows][columns].alpha = MIN(bricksVal,brick_description[rows][columns]);
            }
        }
    } //if
    if (livesVal != -1)
    {
        for (int i=0; i<LIVES; i++)
        {
            lives[i].alpha=livesVal;
            lives[i].transform = CGAffineTransformIdentity;
        }
    } //if
}
// called by App delegate when the app is interrupted (home key pressed..)
- (void) saveState
{
    NSLog(@"interrupted");
    kGamePause=1;
    labelPause.hidden = NO;
}
- (void)viewDidUnload {
}
- (void)dealloc {
    [ball release];
    [paddle release];
    [labelScore release];
    [song release];
    [explodeImages release];
    [rainDrops release];
    AudioServicesDisposeSystemSoundID(hitSound);
    AudioServicesDisposeSystemSoundID(missSound);
    AudioServicesDisposeSystemSoundID(clapSound);
    AudioServicesDisposeSystemSoundID(explodeSound);
    AudioServicesDisposeSystemSoundID(booSound);
    AudioServicesDisposeSystemSoundID(holycowSound);
    AudioServicesDisposeSystemSoundID(boingSound);
    AudioServicesDisposeSystemSoundID(metalSound);
    AudioServicesDisposeSystemSoundID(gotlifeSound);
    AudioServicesDisposeSystemSoundID(missileSound);
    // anything else that I missed?
    [super dealloc];
}
@end
В этом коде сперва синтезируются переменные.
#import "BricksViewController.h"
#import "GBMusicTrack.h"
@implementation BricksViewController
@synthesize ball;
@synthesize paddle;
@synthesize cloud;
@synthesize labelScore;
@synthesize labelCurrentLevel;
@synthesize labelPause;
@synthesize labelGameOver;
@synthesize explodeView;
@synthesize backgroundImageView;
@synthesize missileView;
@synthesize haveMissileView;
@synthesize hackView;
@synthesize volumeView;
@synthesize infoView;
@synthesize helpView;
@synthesize hitSound;
@synthesize missSound;
@synthesize explodeSound;
@synthesize clapSound;
@synthesize boingSound;
@synthesize booSound;
@synthesize holycowSound;
@synthesize metalSound;
@synthesize gotlifeSound;
@synthesize missileSound;
@synthesize gameTimer;
@synthesize backgroundTimer;
@synthesize harderTimer;
@synthesize rainDrops;
Затем описывается метод viewDidiLoad. Этот метод автоматически вызывается при загрузке игры. В нем мы сперва устанавливаем значения переменным, назначение которых было объяснено выше.
Переменные kBallSpeedX и kBallSpeedY будут иметь интегральные значения, которые определяются с помощью инкримента. Позже вы увидите, как эти значения изменяются на + или _ в зависимости от того как отскакивает и с чем сталкивается мяч. Затем эти значения добавляются к текущим координатам X и Y положения мяча на экране для того, чтобы мяч мог двигаться по экрану.
Обратите внимание, что у некоторых таймеров установлено nil. Это сделано для того, чтобы, когда игра заканчивается или ставится на паузу при следующем старте таймеры возобновляли свой отсчет с нуля. Но вместо того, чтобы определять какой таймер активен, а какой нет, мы просто всегда останавливаем на паузе все таймеры благодаря тому, что устанавливаем им значение nil. Массив backgroundArray инициализируется с несколькими различными картинками. Изображения будут зависеть от того на каком уровне находится игрок.
 Далее прописаны стандартные операции по установке и определению акселеррометра. Каждые 30 секунд проверяется положение акселерометра. когда обнаруживается его изменение, то запускается функция touchesMoved.
Метод initAudioStreams содержит код для загрузки wav-файлов. Здесь используется AudioSystemSound API. Далее подключается фоновая музука. Необходимо добавить, что перед тем как вы получите контроль над всеми айдио-файлами и картинками вам необходимо скопировать их в директорию программы. В конце вызываются методы для инициализации display, lives display, explosion sprites, rain animation и start the timers. Таймеры - самая критичная часть игровой логики, так как в них содержится все управление игрой.
//=============================================================================
- (void)viewDidLoad {
    NSLog(@"Inside ViewDidLoad");
    [super viewDidLoad];
    //************************
    kHackHitCount=0;                // 1=skip levels quickly. 0=normal play
    //************************
    kBallSpeedX = 4;
    kBallSpeedY = 4;
    tBallSpeedX = 0;
    tBallSpeedY = 0;
    kMakeItHarder = 10;             // Time in seconds to make things harder in the game
    kGamePause = 1;
    gameTimer = nil;
    backgroundTimer = nil;
    harderTimer = nil;
    gameLevel =1;
    kMissileFiring=0;
    kMissilesLeft=0;
    kVolumeIsOn=1;
    kMakeItHarder = 60;             // Time in seconds to make things harder in the game
    backgroundArray[0]=@"level1.png";
    backgroundArray[1]=@"level2.png";
    backgroundArray[2]=@"level3.png";
    backgroundArray[3]=@"level4.png";
    [backgroundImageView setImage:[UIImage imageNamed: backgroundArray[gameLevel-1]]];
    // set up the accelerometer - I want to use it to move my paddle
    UIAccelerometer *myAccel = [UIAccelerometer sharedAccelerometer];
    myAccel.updateInterval = 1.0f/30.0f;
    myAccel.delegate=self;
    ballSpeed = CGPointMake(kBallSpeedX,kBallSpeedY);
    score = 0;
    livesleft = LIVES;
    // Initialize all the audio streams that we will be playing
    UInt32 sessionCategory = kAudioSessionCategory_AmbientSound;
    AudioSessionSetProperty (kAudioSessionProperty_AudioCategory,
                             sizeof (sessionCategory),
                             &sessionCategory);
    [self initAudioStreams:&hitSound :@"hitme" :@"wav"];
    [self initAudioStreams:&booSound :@"boo" :@"wav"];
    [self initAudioStreams:&missSound :@"die" :@"wav"];
    [self initAudioStreams:&clapSound :@"applause" :@"wav"];
    [self initAudioStreams:&explodeSound :@"explode" :@"wav"];
    [self initAudioStreams:&boingSound :@"boing" :@"wav"];
    [self initAudioStreams:&holycowSound :@"holycow" :@"wav"];
    [self initAudioStreams:&metalSound :@"metalsound" :@"wav"];
    [self initAudioStreams:&gotlifeSound :@"gotlife" :@"wav"];
    [self initAudioStreams:&missileSound :@"missile" :@"wav"];
    // I am blindly using GBMusicTrack for now based on their instructions...
    song = [[GBMusicTrack alloc] initWithPath:[[NSBundle mainBundle] pathForResource:@"background" ofType:@"mp3"]];
    //[song setRepeat:YES];
    //[song play];
    [self initBricks];      // fill in and display all the bricks
    [self initLives];
    [self initExplosion];   // pre-load explosion array
    [self initRain];        // pre-load rain array
    [self initTimer];       // start game timers and we are on our way!
    UIImage *image = [UIImage imageNamed:@"missile.png"];
    missileView = [[[UIImageView alloc] initWithImage:image] autorelease];
    missileView.hidden=YES;     // load the missile image, hide it for now
    [self.view addSubview:missileView];
    ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);
}
Далее по коду. Метод initAudioStreams управляет wav-файлами. Он точно следдует процедурам Эпл.Метод playAudio запускает AudioServicesPlaySystemSound, но только, если переменная kVolumeIsOn равна 1. В игре, если вы нажмете на иконку звука, то сможете его включить или выключить, ищменяя значение переменной kVolumeIsOn между 0 и 1. Стоит заметить, что звуки и анимация в игре проигрываются асинхронно. Метод initExplosion создает массив из 34 картинок UIImages. Каждая картинка содержит кадр изображения взрыва. Для анимации этих картинок используется UIKit animation API.

//=============================================================================
// play sounds only if volume is not muted
-(void) playAudio: (SystemSoundID) soundId
{
    if (kVolumeIsOn) {AudioServicesPlaySystemSound(soundId);}
}
//=============================================================================
// convenience function. Pass it a reference to the SystemSoundID handle, the file and its type and it
// creates an audio stream ready to play
- (void) initAudioStreams: (SystemSoundID *)soundId :(NSString *)soundFile  :(NSString *) type
{
    NSString *audioPath=[[NSBundle mainBundle] pathForResource:soundFile ofType:type];
    CFURLRef audioURL = (CFURLRef) [NSURL fileURLWithPath:audioPath];
    AudioServicesCreateSystemSoundID(audioURL, soundId);
}
//=============================================================================
// pre-load the explosion effect into an array.
- (void) initExplosion
{
    explodeImages = [[NSMutableArray alloc] initWithCapacity: 34];
    for (int i = 1; i <= 34; i++)
    {
        NSString *imageName = [NSString stringWithFormat:@"boom%d.png",i];
        UIImage *image = [UIImage imageNamed:imageName ];
        [explodeImages addObject: image];
    }
}

ht, this is why I am Далее создаются массивы для размещения блоков на уровнях Каждый уровень имеет 2D массив вида (hence brick_level[levels][rows][col]). Каждый номер представляет свой тип блока. В зависимости от текущего уровня в brick_description копируется индекс массива  и затем по номерам блоки выводятся на экран. Если вы захотите добавить новые уровни, то просто добавьте матрицу сюда и увеличте число GAME_LEVELS в файле BricsViewController.h.

//=============================================================================
// load bricks layout. This array controls what sort of bricks show at each
// cell of the brick matrix
- (void) initBrickLayout
{
    NSLog(@"init brick layout");
    NSLog(@"Current Game Level is %d",gameLevel);
    // You can modify these arrays to respresent the bricks
    // as you want for each level. Each block is for a particular level.
    // 0 = no brick at this cell
    // 1 = normal brick - you hit it, it explodes and you add to your score
    // 2 = metal brick - you can't kill it - the ball will hit and bounce off faster
    // 3 = super brick - you hit it, it explodes and you will get a new life (if you are not at max)
    // 4 = missile brick - you get 5 missiles each time you hit this one
    int bricklevel[GAME_LEVELS][BRICKS_ROWS][BRICKS_COLUMNS] = {
    {
            // level 1
            {2,0,0,1,0,0,2},
            {0,0,1,1,1,0,0},
            {0,1,4,0,4,1,0},
            {1,1,3,0,3,1,1},
            {1,1,1,0,1,1,1},
            {0,1,3,0,3,1,0},
            {0,0,4,1,4,0,0},
            {2,0,0,1,0,0,2},
    },
    {
            // level 2
            {3,2,1,1,1,2,3},
            {1,1,1,1,1,1,1},
            {2,1,1,1,1,1,2},
            {1,1,1,1,1,1,1},
            {1,1,2,3,2,1,1},
            {4,1,1,2,1,1,4},
            {1,2,1,1,1,2,1},
            {2,1,1,1,1,1,2},
    },
    {
            // level 3
            {0,0,0,3,0,0,0},
            {0,0,1,1,1,0,0},
            {0,0,1,1,1,0,0},
            {0,2,1,1,1,2,0},
            {0,1,1,1,1,1,0},
            {2,1,1,1,1,1,2},
            {1,4,1,1,1,4,1},
            {1,1,2,1,2,1,1},
    },
    {
            // level 4
            {0,1,0,3,0,1,0},
            {2,1,1,1,1,4,2},
            {0,1,1,1,1,1,0},
            {0,0,1,1,1,0,0},
            {0,0,1,1,1,0,0},
            {0,1,1,1,1,1,0},
            {2,4,1,3,1,1,2},
            {1,0,1,0,1,0,1},
    },
    };
    NSLog (@"Copying level %d(-1) to bricks description", gameLevel);
    memcpy (brick_description, bricklevel[gameLevel-1], sizeof(brick_description)); // C still rocks. W00t :-)
}

 Далее идут методы анимации дождя. Метод initRain в случайном порядке создает 50 дождевых капель на экране. Они появляются с индексом 1 над фоновым изображением, которое имеет индекс 0 и падают за блоками. В зависимости от уровня дождевые капли показывается или скрываются с экрана (rain.hidden  = YES | NO). Метод moveRain просто увеличивает координаты положения каждой капли на экране для анимации дождя.

//=============================================================================
// init rain array
- (void) initRain
{
    NSLog(@"Inside initRain");
    NSMutableArray *array = [[NSMutableArray alloc] init];
    self.rainDrops = array;
    [array release];
    UIImage *rainImage = [UIImage imageNamed:@"raindrop.png"];
    UIImageView *rainView;
    // randomly place 50 rain drops across the screen
    for (int i = 0; i< 50; i++)
    {
        rainView= [[UIImageView alloc] initWithImage:rainImage];
        rainView.alpha=0.3;
        rainView.hidden=YES;
        int x = arc4random()% (int)self.view.bounds.size.width;
        int y = arc4random()% (int)self.view.bounds.size.height;
        rainView.center = CGPointMake (x,y);
        // the background image is at index 0, make rain at 1,
        // so it is behind the bricks
        [self.view insertSubview:rainView atIndex:1];
        [self.rainDrops addObject: rainView];
        [rainView release];
    }
}
// we need to hide the rain for layers that don't have rain
- (void) hideRain
{
    for (UIImageView *rain in rainDrops)
    {
        rain.hidden=YES;
    }
}
// animate the rain across the screen
-(void)moveRain{
    for (UIImageView *rain in rainDrops) {
        CGPoint newCenter = rain.center;
        newCenter.y = newCenter.y +2;
        newCenter.x = newCenter.x +1;
        rain.hidden = NO;
        if (newCenter.y > self.view.bounds.size.height){
            newCenter.y=0;
            newCenter.x = arc4random()%(int)self.view.bounds.size.width;
        }
        if (newCenter.x > self.view.bounds.size.width){
            newCenter.x =0;
        } else if (newCenter.x < 0) {
            newCenter.x = self.view.bounds.size.width;
        }
        rain.center = newCenter;
    }
}


Ракеты являются частью UIView. Они выстреливаются и движутся вверх, если вы дважды нажмете на ракетку. Это вызовет запуск метода launchMissile. Когда ракета доходит до верха экрана или врезается в блок (здесб используется логика столкновений объектов), то работа метода завершается.

//=============================================================================
// launches a missile
// you would think we can use Core Graphics animate to automatically do this
// but we can't as we also need to detect if it hit a brick with CGRectIntersect
// The automatic animation does not allow for detection if intersects, so we need
// to do it ourselves, or, use auto animation in small steps.
- (void) launchMissile;
{
    if (!kMissilesLeft) {NSLog(@"You don't have missiles");return;}
    if (kMissileFiring) {return;} // only allow 1 missile at a time
    haveMissileView.hidden=NO;
    kMissileFiring=1;
    missileView.hidden=NO;
    [self playAudio:missileSound];
    missileView.center=CGPointMake(paddle.center.x, paddle.center.y-10);
    kMissilesLeft--;
    if (!kMissilesLeft) {haveMissileView.hidden=YES;}
    NSLog(@"You have %d missiles left",kMissilesLeft);
}
-(void) missileFinished
{
    missileView.hidden=YES;
    kMissileFiring=0;
}

Мы загружаем массив блоков с помощью картинок и затем вызываем метод initBrickLayout, который копирует нужное описание матрицы блоков в массив brick_description. Здесь логика базируется на описание массива brick_array для уровня.

//=============================================================================
// load the bricks images and create the bricks matrix. Also create the lives display
- (void) initBricks
{
    NSLog(@"Inside initBricks");
    brickImages[0]=@"brick2.png"; //normal
    brickImages[1]=@"brick2.png";
    brickImages[2]=@"steel.png"// cant destroy
    brickImages[3]=@"brick3.png"; // special powers - life
    brickImages[4]=@"brick1.png"; // special powers - missiles
    [self initBrickLayout];

Поскольку я ошибочно нарисовал блоки  большего размера, чем они отображаются в игре, то я решил просто уменьшить их размеры с помощью программного кода (линии кода myFrame.size.width/height). В идеале вам эти строки будут не нужны.
Итак мы строим матрицу блоков и отображаем их согласно их типу в brick_description. Линяя кода MIN(1, brick_description….) выводит на экран любые блоки, которые не имеют 0 в матрице описаний. Если стоит 0, то значит бюлока нет. Если блок нормальный (1) или супер-блок (2, 3, 4), то они будут отображены, так как  имеют alpha 1  (alpha означает opacity. 0 = transparent. 1 = opaque). Наконец каждый блок выводится на экран как new view (self.view addSubview).

    for (int rows=0; rows<BRICKS_ROWS; rows++)
    {
        for (int columns=0; columns<BRICKS_COLUMNS; columns++)
        {
            int imageType = brick_description[rows][columns];
            UIImage *image = [UIImage imageNamed:brickImages[imageType]];
            bricks[rows][columns] = [[[UIImageView alloc] initWithImage:image] autorelease];
            CGRect myFrame = bricks[rows][columns].frame;
            myFrame.size.width=30;
            myFrame.size.height=10;
            // position the bricks so there is some space between them
            myFrame.origin = CGPointMake(columns*42+5,rows*17+80);
            //NSLog(@"Width:%f, height:%f",myFrame.size.width, myFrame.size.height);
            bricks[rows][columns].frame= myFrame;
            bricks[rows][columns].alpha = MIN(1,brick_description[rows][columns]);
            // every brick is a subview - show it
            [self.view addSubview:bricks[rows][columns]];
        } //cols
    } //rows
}

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

//=============================================================================
// load the bricks images and create the bricks matrix. Also create the lives display
- (void) remapBricks
{
    NSLog(@"Inside remapBricks");
    [self initBrickLayout];
    for (int rows=0; rows<BRICKS_ROWS; rows++)
    {
        for (int columns=0; columns<BRICKS_COLUMNS; columns++)
        {
            int imageType = brick_description[rows][columns];
            [bricks[rows][columns] setImage:[UIImage imageNamed: brickImages[imageType]]];
            bricks[rows][columns].alpha = MIN(1,brick_description[rows][columns]);
        } //cols
    } //rows
}


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


//=============================================================================
// Initializes  the lives display on top left
- (void) initLives
{
    NSLog(@"Inside initLives");
    // load lives array and create the lives display
    for (int i=0; i<LIVES;i++)
    {
        UIImage *image = [UIImage imageNamed:@"lives.png"];
        lives[i] = [[[UIImageView alloc] initWithImage:image] autorelease];
        CGRect myFrame = lives[i].frame;
        // position the lives so there is some place between them
                myFrame.origin = CGPointMake(i*40 % 160 ,20+(20* (i/4)));
        lives[i].frame= myFrame;
        // every life is a subview - show it
        [self.view addSubview:lives[i]];
    }
}

Вся игровая логика происходит внутри метода gameTimer. gameTime вызывается каждые 30 секунд. Поэтому каждую 1/30 секунды программа проверяет местоположение мяча, ракет, ракетки, какой блок разбит, обновляет очки и статус игры. BackgroundTimer - управляет анимацией, движением облаков и функцией moveRain, он также управляет движением ракет.

//=============================================================================
// Initializes  all timers,
// gameTimer: The core timer that controls game logic
// backgroundTimer: animates the background elements
// harderTimer: after X seconds, it makes the game harder
- (void) initTimer
{
    NSLog(@"Inside initTimer");
    [gameTimer invalidate];
    [backgroundTimer invalidate];
    [harderTimer invalidate];
    gameTimer=[NSTimer scheduledTimerWithTimeInterval:1.0/30.0 target:self selector:@selector(playGame:) userInfo:nil repeats:YES];
    backgroundTimer=[NSTimer scheduledTimerWithTimeInterval:1.0/30.0 target:self selector:@selector(adjustBackground:) userInfo:nil repeats:YES];
    harderTimer = [NSTimer scheduledTimerWithTimeInterval:kMakeItHarder target:self selector:@selector(makeItHarder:) userInfo:nil repeats:YES];
}

После того, как время таймера истекает игра каждый раз усложняется, увеличивая свою скорость. Это делает метод makeItHarder.


//=============================================================================
// This is the callback that is called when the game needs to get harder
- (void) makeItHarder:(NSTimer *)timer
{
    if (kGamePause) {return;}
    [self playAudio:holycowSound];
    NSLog (@"TIME TO MAKE IT HARDER FOR LEVEL:%d", gameLevel);
    //NSLog(@"*********************Inside MakeItHarder, bumping up ball speed");
    NSLog(@"Original SpeedX:%d, SpeedY:%d",kBallSpeedX,kBallSpeedY);
    if (kBallSpeedX<8) {kBallSpeedX++;}
    if (kBallSpeedY<8) {kBallSpeedY++;}
    ballSpeed.x = (ballSpeed.x <0) ? kBallSpeedX:-kBallSpeedX;
    ballSpeed.y = (ballSpeed.y <0) ? kBallSpeedY:-kBallSpeedY;
    NSLog(@"Inside MakeItHarder, bumping up ball speed to X:Y %d:%d", kBallSpeedX, kBallSpeedY);
    ballSpeed = CGPointMake(kBallSpeedX,kBallSpeedY);
}

Если игра ставится на паузу, то вся анимация на экране замерает.



//=============================================================================
// This is the callback that backgroundTimer calls at every interval
- (void) adjustBackground: (NSTimer *)timer
{
    //if (kGamePause) {return;}
if (gameLevel % 2)
{
    float newposx;
    cloud.hidden=NO;
    [self hideRain];
    newposx = cloud.center.x+0.3;  // move cloud image across the screen smoothly
    if (cloud.center.x > self.view.bounds.size.width+80) {newposx=-64;}  // wrap it around
    cloud.center = CGPointMake(newposx, cloud.center.y);
}
else
{
    cloud.hidden=YES;
    [self moveRain];
}
Далее описывается движение ракеты по экрану.
    if (kMissileFiring)
    {
        int missileY;
        missileY=missileView.center.y-10;
        if (missileY<0)
        {
            [self missileFinished];
        }
        else
        {
            missileView.center=CGPointMake(missileView.center.x, missileY);
        }
    } // kMissileFiring
}
После этого описывается метод рестарта игры resetGame. Обычно это метод используется когда игрок погиб или вся игра завершена.
//=============================================================================
// resets game back to normal.
-(void) resetGame
{
    NSLog(@"Inside reset Game");
    score = 0;
    livesleft = LIVES;
    kBallSpeedX = 4;
    kBallSpeedY = 4;
    ballSpeed = CGPointMake(kBallSpeedX,kBallSpeedY);
    gameLevel = 1;
    labelCurrentLevel.text = [NSString stringWithFormat:@"%d",gameLevel];
    labelScore.text = [NSString stringWithFormat:@"%d", score];
    kMissileFiring=0;
    kMissilesLeft=0;
    haveMissileView.hidden=YES;
    for (UIImageView *rain in rainDrops) { rain.hidden = YES; }
    [backgroundImageView setImage:[UIImage imageNamed: backgroundArray[gameLevel-1]]];
    NSLog(@"Calling initBricks with level:%d",gameLevel);
    [self remapBricks];
    [self initTimer];
    kGamePause=1;
    ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);
    NSLog(@"*****GAME RESET***********");
}


Метод explodeBric описывает анимацию взрыва блока. Он получает координаты X и Y метоположения блока и использует UIView animation для анимации 34 акартинок с изображением взрыва в течении 1 секунды.
// This is called when the ball hits a solid brick. It shows an explosion
-(void) explodeBrick: (int) y : (int) x
{
    NSLog(@"Explosion!!!!!");
    // Position the explosion so it is properly centered on the brick
    explodeView.center = CGPointMake(bricks[y][x].center.x, bricks[y][x].center.y);
    // bring the explosion view to the front
    [self.view bringSubviewToFront:explodeView];
    explodeView.animationImages = explodeImages;
    explodeView.animationDuration = 1;
    explodeView.animationRepeatCount = 1;
    [explodeView startAnimating];
}
В конце все переменные освобождается и используется метод dealloc.


- (void)viewDidUnload {
}
- (void)dealloc {
    [ball release];
    [paddle release];
    [labelScore release];
    [song release];
    [explodeImages release];
    [rainDrops release];
    AudioServicesDisposeSystemSoundID(hitSound);
    AudioServicesDisposeSystemSoundID(missSound);
    AudioServicesDisposeSystemSoundID(clapSound);
    AudioServicesDisposeSystemSoundID(explodeSound);
    AudioServicesDisposeSystemSoundID(booSound);
    AudioServicesDisposeSystemSoundID(holycowSound);
    AudioServicesDisposeSystemSoundID(boingSound);
    AudioServicesDisposeSystemSoundID(metalSound);
    AudioServicesDisposeSystemSoundID(gotlifeSound);
    AudioServicesDisposeSystemSoundID(missileSound);
    // anything else that I missed?
    [super dealloc];
}
@end


Далее описывается механизм определения столкновений объектов.

//=============================================================================
// This is the heart of the game. Checks for collisions, hits, misses etc. Can be cleaned up better
- (void) playGame:(NSTimer *)timer
{
    static int hack_hit_count = 0;
    if (kGamePause) {return;}
    //First Check for brick collision
    // basically, we iterate through the array of bricks
    // and compare if the ball location intersects with any of the bricks location
    // From a display perspective, we are not really deleting the brick. We are simply making its alpha (transparency)
    // to 0 so it looks like its gone. Therefore, when we check if the ball has hit a brick, we also need to check if
    // the brick is not transparent. If it is, its been hit before.

Для каждого блока проверяется перескся ли он с мячем. Для этого используется  функция CGRectIntersectsRect, которая просто yes или в зависимости от того пересеклись ли два объекта или нет. Однако помните, что мяч - это шар, а не прямоугольник поэтому для него используется маленьки трюк. Проверяется sub-frame блока, который на 3 пикселя и 1 пиксель меньше и находится в фрейме блока. ballHitBrickбудет равна 1, если мяч попадет по блоку. Так же логика используется и для попадания ракет.

#pragma mark Brick Collision logic
    int youWin=1;      // if this is 0, then there is atleast one brick that has not been hit. If 1, all bricks
                       // have been hit.
    for (int y=0; y<BRICKS_ROWS; y++)
    {
        for (int x=0; x<BRICKS_COLUMNS; x++)
        {
            // make sure the ball intersects the brick propoerly.
            // since the ball is circular,  take a hit only if its 3x1 pixels into the brick intersection
            CGRect brick_inside = bricks[y][x].frame;
            brick_inside.size.width -=3;
            brick_inside.size.height-=1;
            brick_inside.origin.x +=3;
            brick_inside.origin.y +=1;
                         int ballHitBrick = CGRectIntersectsRect(ball.frame, brick_inside);
            int missileHitBrick = (CGRectIntersectsRect(missileView.frame, bricks[y][x].frame));


Сперва мы рассчитываем все столкновения, если alpha блока равна 1, блок являнтся нормальным блоком, стальным блоком или супер-блоком.

if ((bricks[y][x].alpha==1) && (brick_description[y][x]>=NORMAL_BRICK))
{

Если мяч или ракета попали по блоку то мы имеем попадание.


if (brick_description[y][x] !=METAL_BRICK) {youWin=0;}
 if (ballHitBrick || (missileHitBrick) && (missileView.hidden==NO))
 {

Для разбиваемых блоков мы показываем анимацию взрыва.


// Coming here means that the ball has hit a brick that has not been hit before
 // or, the missile hit a brick
if ((brick_description[y][x]==NORMAL_BRICK) || (brick_description[y][x]>=SUPER_BRICKS))
{

Если вы попали по блоку, то ваши очки увеличиваются на 10.


NSLog(@"YOU HIT:%d:%d",y,x);
if (kHackHitCount) {hack_hit_count++;}
//NSLog (@"HACK HIT COUNT %d & kHack:%d", hack_hit_count, kHackHitCount);
score += 10;
labelScore.text = [NSString stringWithFormat:@"%d", score];

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

if (missileHitBrick)
{
    NSLog(@"MISSILE HIT");
    missileView.hidden=YES; // after a hit, hide the missile
    kMissileFiring=0;   // allow other missiles to be fired
}
[self playAudio:explodeSound];
[self explodeBrick :y :x];

Анимируем плавное исчезновение блока.

bricks[y][x].alpha -= 0.1; // reduce alpha right away by a pt.
                          // just in case you hit it again before anim begins....
[UIView beginAnimations:@"fadebrick" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[UIView setAnimationDuration:0.8];
[UIView setAnimationDelegate:self];
bricks[y][x].alpha=0;
[UIView commitAnimations]; // fade out killed brick

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


if (brick_description[y][x]==LIFE_BRICK) // this means you get a life
{
    if (livesleft<LIVES)
    {
        lives[livesleft].alpha=1;
        lives[livesleft].transform = CGAffineTransformIdentity;
        ++livesleft;
        [self playAudio:gotlifeSound];
    }
    NSLog(@"you got a life");
}

Если попали по ракетному блоку, то добавляем игроку 5 ракет и включаем иконку запуска ракет.




    else if (brick_description[y][x]==MISSILE_BRICK) // you get a few missiles
    {
        kMissilesLeft+=5;
        haveMissileView.hidden=NO;
        NSLog(@"You have 5 more missiles. Your missile count is %d", kMissilesLeft);
    }
} // normal brick


Если ударили по стальному блоку то проигрываем звук и добавляем случайное дельта-приращение скорости к координате X мяча.
else
{ // you hit a steel bar
    [self playAudio:metalSound];
    tBallSpeedX = 5 + arc4random() % 5; // let the ball 'shoot' out faster if it hits metal
    //if (ballSpeed.x < 0) {tBallSpeedX = -tBallSpeedX;}
    NSLog(@"You hit steel. Ball speed temporarily upped to %d",tBallSpeedX);
}


Если мяч попал по блоку, то рассчитываем его отскок.

                if (!missileHitBrick) //don't reverse ball direction if a missile hit
                {
                    if ((ballSpeed.x>0) && (ballSpeed.y>0)) { ballSpeed.y = -ballSpeed.y;}
                    else if ((ballSpeed.x>0) && (ballSpeed.y<0)) { ballSpeed.y = -ballSpeed.y;}
                    else if ((ballSpeed.x<0) && (ballSpeed.y>0)) { ballSpeed.x = -ballSpeed.x;}
                    else if ((ballSpeed.x<0) && (ballSpeed.y<0)) { ballSpeed.y = -ballSpeed.y;}
                    if (ballSpeed.x <0) tBallSpeedX = -tBallSpeedX;
                    if (ballSpeed.y <0) tBallSpeedY = -tBallSpeedY;
                }
            } // CGRectIntersect
        } // if you hit a solid brick
    } //x
} //y


После проверки столкновений проверяются условия игры.Этот фрагмен проверяет включен ли Хак-режим.

#pragma mark Winning Logic
    /*********** You've won **********************/
    if ((kHackHitCount) && (hack_hit_count>=HACK_HITCOUNT))
    {
        hack_hit_count=0;
        youWin=1;
        NSLog(@"**Hack hitcount reached");
    }


После проверки, если youWin равна 1, то игрок победил. После этого он переходит на следующий уровень или выводится сообщение об окончании игры. В люом случае все таймеры останавливаются.

if (youWin) // all bricks over
{
    NSLog(@"Inside youWin");
    [self resetAlphasforLives:-1 forBricks:0]; // remove all bricks, don't touch lives
    [self playAudio:clapSound];
    if (gameLevel == GAME_LEVELS)
    {
        NSLog(@"*************Inside game over***************");
        labelGameOver.text = @"You Rock!";
        labelGameOver.hidden=NO;                         // this will display the Game Over message
        [gameTimer invalidate]; gameTimer=nil;              // kill all timers - game is over
        [backgroundTimer invalidate]; backgroundTimer=nil;
        [harderTimer invalidate]; harderTimer=nil;
        return;
    }
    else {
        NSLog(@"Inside move to next nevel");
        labelGameOver.text = [NSString stringWithFormat:@"Level %d", gameLevel+1];
        labelGameOver.hidden=NO;
        [gameTimer invalidate]; gameTimer=nil;
        [backgroundTimer invalidate]; backgroundTimer=nil;
        [harderTimer invalidate]; harderTimer=nil;
        return;
    }
}

Если мяч попал по ракетке, то мы проверяем куда он попал.


#pragma mark Paddle hits ball logic
    // Now lets check if the paddle hit the ball
    if (CGRectIntersectsRect(ball.frame, paddle.frame) && ball.center.y<paddle.center.y)
    {
        [self playAudio:hitSound];
        NSLog(@"HIT");
        ballSpeed.y = -ballSpeed.y;
        // let's check if the paddle hit the ball on its edge. If so, we want to make sure
        // the ball bounces correctly (example, nudge from left edge of paddle should cause the
        // ball to bounce back up and left, not right. And so forth)
        float edge = ball.center.x-paddle.center.x;
        // if this value is 20 or more, its an edge hit. I found this threshold out
        // by priniting the difference between paddle center and ball center for various hits.
        // Pretty unscientific, I'll admit.


Проверяем ушел ли мяч за ракетку.

#pragma mark Edge hit logic
        if (abs(edge)>=20)
        {
            NSLog(@"Edge Hit Detected");
            int paddleCenterWidth = paddle.frame.size.width/2;
            if (    (paddle.center.x+paddleCenterWidth < self.view.bounds.size.width-10)
                 && (paddle.center.x - paddleCenterWidth > 10))
            {
                ballSpeed.x = (edge<0)? -kBallSpeedX:kBallSpeedX;
                NSLog(@"Edge skew activated:Ball Speed set to %f",ballSpeed.x);
            }
        }
        tBallSpeedX = 0; tBallSpeedY =0;
        labelScore.text = [NSString stringWithFormat:@"%d", score];
    }


Проверяем удар мяча о границу экрана. self.view.bounds.size - лучший способ определить размер игровой области View или ввести их с помощью переменных значение 320 и 480.

#pragma mark Ball bouncing off screen check
    // now check if the ball is going beyond screen boundaries, if so,
    // bring it back
    if (ball.center.x+ballSpeed.x>self.view.bounds.size.width-4 || ball.center.x+ballSpeed.x-4<0)
    {[self playAudio: boingSound]; ballSpeed.x = -ballSpeed.x; tBallSpeedX=0; tBallSpeedY=0;}
    if (ball.center.y+ballSpeed.y-4 >self.view.bounds.size.height || ball.center.y+ballSpeed.y-4 <0)
        {ballSpeed.y = -ballSpeed.y;[self playAudio: boingSound]; tBallSpeedX=0; tBallSpeedY=0;}
    int x,y;
    x= ball.center.x+ballSpeed.x+tBallSpeedX;
    y = ball.center.y+ballSpeed.y+tBallSpeedY;
    if (x<0) { x = 0;}
    if (y<0) { y = 0;}
    // catchall
    if (x>self.view.bounds.size.width-4) { x=self.view.bounds.size.width-4;}
    if (y>self.view.bounds.size.height-4) { y=self.view.bounds.size.height-4;}
    ball.center = CGPointMake(x, y);
Если мы упустили мяч, то уменьшить число жизней.


#pragma mark Paddle missing the ball logic
    // When you miss the ball, we want the ball to fall off the screen, not just disappear immediately
    // when your paddle misses. So we set this youmissed flag to 1 when the paddle misses and the ball keeps going down
    // till it reaches the edge of the screen. When it does, we decrease the score, kill a life and play sound
    static int youmissed=0;
    if ((ball.center.y > paddle.center.y) && (youmissed==0))
    {
        youmissed=1;
        labelScore.text = [NSString stringWithFormat:@"%d", score];
    }

А здесь проверяется координата Y мяча.


if (ball.center.y >= self.view.bounds.size.height-4) // have we run off screen?
    {
        score -= 10;
        labelScore.text = [NSString stringWithFormat:@"%d", score];
        --livesleft;
        // if you lose a life, lets rotate the life icon around and then make it disappear
        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDuration:0.2];
        [UIView setAnimationCurve:UIViewAnimationCurveLinear];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(lifeDeathAnimeDidStop)];
        CGAffineTransform transform1 = CGAffineTransformMakeScale(1,1);
        CGAffineTransform transform2 = CGAffineTransformMakeRotation(179.9 * M_PI /180.0);
        lives[livesleft].transform = CGAffineTransformIdentity;
        lives[livesleft].transform = CGAffineTransformConcat(transform1, transform2);
        [UIView commitAnimations];
        // After you die, set ball back to center
        ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);
        // and make sure the ball goes down
        ballSpeed.y = kBallSpeedY;
        [self playAudio:missSound];
        youmissed=0;
        kGamePause = 1; // lets freeze the game till he taps
        labelPause.hidden = NO;


Здесь устанавливается длительность анимации.


[UIView beginAnimations:nil context:nil];
        [UIView setAnimationDuration:0.2];
        [UIView setAnimationCurve:UIViewAnimationCurveLinear];


А здесь определяются нажатия на экран.


/=============================================================================
// used to detect any touches on the screen and trigger different operations
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // I am not forcing you to touch the paddle. You can touch and drag anywhere to move the paddle
    // example, its easier to drag below the paddle that way you see the paddle as well
    UITouch *mytouch = [[event allTouches] anyObject];
Эта часть возвращает координаты места нажатия на экран.


touch = paddle.center.x - [mytouch locationInView:mytouch.view].x;
NSArray *allTouches = [touches allObjects];
// Did you tap on the info view ?
if (CGRectContainsPoint(infoView.frame, [[allTouches objectAtIndex:0] locationInView:self.view]))
{
    if (helpView.hidden=YES)
    {
        helpView.hidden=NO;
        [self.view bringSubviewToFront:helpView];
        kGamePause=1; // pause the game when you show help
        return;
    }
}
Здесь определяется нажатие на иконку.

// did you tap on the help screen after it was displayed?
    if ((CGRectContainsPoint(helpView.frame, [[allTouches objectAtIndex:0] locationInView:self.view]) &&(helpView.hidden==NO)))
    {
        helpView.hidden=YES;
        return;
    }
Здесь мы прячем экран подсказки.

// did you tap on the volume icon?
    if (CGRectContainsPoint(volumeView.frame, [[allTouches objectAtIndex:0] locationInView:self.view]))
    {
        NSLog(@"Volume tapped");
        kVolumeIsOn ^=1;
        // toggle the volume image and status on and off with each tap
        if (kVolumeIsOn)
        {
            [volumeView setImage:[UIImage imageNamed: @"volumeon.png"]];
        }
        else {
            [volumeView setImage:[UIImage imageNamed: @"volumeoff.png"]];
        }
        return;
    }
Тоже самое делаем для иконки звука.

if (labelGameOver.hidden == NO) // game was over so restart if user touches anywhere
    {
        if ((gameLevel == GAME_LEVELS) || (livesleft == 0) )
        // game is over, so restart from level 1
        {
            NSLog(@"After touch: Game is over, reset");
            labelGameOver.hidden = YES;
            // reset all parameters of the game and lets go again
            [self resetAlphasforLives:1 forBricks:-1];
            [self resetGame];
            ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);
            // make the ball go down
            if (ballSpeed.y < 0) ballSpeed.y = -ballSpeed.y;
            labelScore.text = [NSString stringWithFormat:@"%d", score];
        // we need to restart the timers, because we killed them when the game got over
        //[self initTimer];
        }
Здесь осуществляется переход на следующий уровень или завершение игры.

else // go to next level
    {
        NSLog(@"inside touchesBegan:next level");
        gameLevel++;
        [backgroundImageView setImage:[UIImage imageNamed: backgroundArray[gameLevel-1]]];
        labelCurrentLevel.text = [NSString stringWithFormat:@"%d",gameLevel];
        labelGameOver.hidden=YES;
        ball.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2+30);
        [self initTimer];
        [self remapBricks];
    }
}
Переход на другой уровень.

if (kGamePause) // if so, then I need to resume with touch
    {
        [harderTimer invalidate]; // if you paused, don't keep the timer for making it harder alive.
        kGamePause = 0;
        labelPause.hidden=YES;
        [self.view bringSubviewToFront:helpView];
        harderTimer = [NSTimer scheduledTimerWithTimeInterval:kMakeItHarder target:self selector:@selector(makeItHarder:) userInfo:nil repeats:YES];
    }
А здесь проверяется находится ли игра на паузе.

else
    {
        // double tapping shoots a missile
        if ([mytouch tapCount] == 2)
        {
            NSLog(@"Tap Twice - Missile Launch");
            [self launchMissile];
        }
        // triple tapping in the lower left part of the screen toggles 'hack mode'
        else if (([mytouch tapCount]==3) &&
                 (CGRectContainsPoint(hackView.frame, [[allTouches objectAtIndex:0] locationInView:self.view])))
        {
            NSLog(@"Tap thrice - HACK MODE!!");
            kHackHitCount ^=1;
            if (kHackHitCount) {hackView.hidden=NO;} else {hackView.hidden=YES;}
        } // tapcount=3
    } // not paused
}
Наконец мы определяем число нажатий.
// this is called when I drag my finger after first touching the screen
// this will move the paddle
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *mytouch = [[event allTouches] anyObject];
    float distance = ([mytouch locationInView:mytouch.view].x + touch) - paddle.center.x;
    float newx = paddle.center.x + distance;
    // we want to make sure the paddle is not dragged off screen, so limit its movement to a point
    // where the full paddle is visible when dragged
    int paddleCenterWidth = paddle.frame.size.width/2;
    if (newx < paddleCenterWidth) {newx=paddleCenterWidth;}
    if (newx > self.view.bounds.size.width-paddleCenterWidth) {newx=self.view.bounds.size.width-paddleCenterWidth;}
    paddle.center = CGPointMake(newx, paddle.center.y);
}
А здесь нажатия перетаскивания.

// Another way to move the paddle. Just rotate the phone...
- (void) accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)accel
{
    float newPaddleX= paddle.center.x + (accel.x *12);
    int paddleCenterWidth = paddle.frame.size.width/2;
    if (newPaddleX > paddleCenterWidth && newPaddleX < self.view.bounds.size.width-paddleCenterWidth)
        paddle.center=CGPointMake(newPaddleX, paddle.center.y);
}
Весь исходный код игры вы можете скачать здесь.

Комментариев нет:

Отправить комментарий