Данный обучающий курс по созданию игры типа арканоид содержит в себе картинки, звуки и фоновую музыку.
Ниже приведен список ссылок на необходимые для создания игры файлы.
Спрайты взрывов.
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
Элементы игрового экрана.

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

Обработка звуковых файлов перед вставкой в проект.
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 предлагает следующие фреймворки для создания графики и анимции:
- UIKit
- Quartz 2D
- Open GL ES
Наша игра базируется на "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. Скопируйте в него следующий код:
//
// 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)
// other sounds: http://www.wavsource.com/sfx/sfx3.htm
#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определяет сколько блоков нужно будет сбить для перехода на следующий уровень.
Матрица блоков, которую вы видите на каждом уровне представлена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
// 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.
Далее прописаны стандартные операции по установке и определению акселеррометра. Каждые 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);
}
//=============================================================================
// 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];
}
- (
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);
}
Комментариев нет:
Отправить комментарий