加速计简单使用---迷宫游戏
今天通過編寫一個簡單的迷宮游戲,來展示如何使用iPhone的內置加速計.
游戲效果如下圖所示.用戶通過上下左右搖晃屏幕控制這個橙色的pacman挪動,pacman撞到屏幕邊緣或者墻壁(藍色邊框方塊)會反彈,撞到紅色ghost會游戲失敗,直到成功吃到黃色豆子便是游戲通關.整個邏輯很簡單,也是我們小時候暢玩的小游戲.
整個游戲教程分為三個部分:
下面讓我們開始吧.
為了實現游戲效果,當我們旋轉iPhone時,我們不希望我們的app跟著旋轉.因此,我們讓我們的app僅支持橫向.這里我們選擇"Landscape Right".當然你也可以選擇"Landscape Left",根據你個人的喜好.
第一步:搭建UI,給Ghost設置動畫
一.搭建UI
? ? 2.將所需要的圖標導入項目中.根據你自己的個人喜好布局UI.設置wall和ghost的數量和位置.這樣也便決定了你設置游戲的難度.我的布局就姑且如下所示.?
接著將各個控件拖入到ViewController.m中以創建IBOutlet,方便我們超控它們.分別命名為pacman,ghost1,ghost2,ghost3,exit和wall.這些都比較簡單,相信大家都會.然而這里面有一點我強調一下,就是關于wall,因為數量較多,我們將采用另一種方式,就是把這些wall對象統一和NSArray對象進行相關聯.這樣看起來較為清爽,也方便我們統一操作.連線時,connection選擇Outlet Collection而非Outlet.
拖完后,ViewController.m中的代碼如下圖所示:
@property (weak, nonatomic) IBOutlet UIImageView *pacman; @property (weak, nonatomic) IBOutlet UIImageView *ghost1; @property (weak, nonatomic) IBOutlet UIImageView *ghost2; @property (weak, nonatomic) IBOutlet UIImageView *ghost3; @property (strong, nonatomic) IBOutletCollection(UIImageView) NSArray *wall; @property (weak, nonatomic) IBOutlet UIImageView *exit;二.導入框架
使用加速計是需要使用到CoreMotion框架的,默認情況下,我們的應用程序不包含此框架,因此需要我們手動導入.直接在TARGETS->General->Linked Frameworks and Libraries 中導入即可.同樣,還要導入另一個很重要的框架:QuartzCore框架.因為我們將使用其中的CABasicAnimation類來創建基本動畫.(當然,因為xcode后來UIKit中有導入框架,也不需要我們手動導入了)l
三:給Ghost設置動畫
ViewController.m中導入頭文件
#import <QuartzCore/CAAnimation.h>接下來給三個ghost添加動畫.
- (void)ghostAnimation {CGPoint origin1 = self.ghost1.center;CGPoint target1 = CGPointMake(self.ghost1.center.x, self.ghost1.center.y+124);CABasicAnimation *bounce1 = [CABasicAnimation animationWithKeyPath:@"position.y"];bounce1.fromValue = @(origin1.y);bounce1.toValue = @(target1.y);bounce1.duration = 2; //周期時間bounce1.autoreverses = YES; //自動反轉bounce1.repeatCount = HUGE_VALF; bounce1.fillMode = kCAFillModeForwards; //動畫完成不移除bounce1.removedOnCompletion = NO; //退到后臺不被系統暫停[self.ghost1.layer addAnimation:bounce1 forKey:@"position"];CGPoint origin2 = self.ghost2.center;CGPoint target2 = CGPointMake(self.ghost2.center.x, self.ghost2.center.y+90);CABasicAnimation *bounce2 = [CABasicAnimation animationWithKeyPath:@"position.y"];bounce2.fromValue = @(origin2.y);bounce2.toValue = @(target2.y);bounce2.duration = 2.5;bounce2.autoreverses = YES;bounce2.repeatCount = HUGE_VALF;bounce2.fillMode = kCAFillModeForwards;bounce2.removedOnCompletion = NO;[self.ghost2.layer addAnimation:bounce2 forKey:@"position"];CGPoint origin3 = self.ghost3.center;CGPoint target3 = CGPointMake(self.ghost3.center.x, self.ghost3.center.y+180);CABasicAnimation *bounce3 = [CABasicAnimation animationWithKeyPath:@"position.y"];bounce3.fromValue = @(origin3.y);bounce3.toValue = @(target3.y);bounce3.duration = 3;bounce3.autoreverses = YES;bounce3.repeatCount = HUGE_VALF;bounce3.fillMode = kCAFillModeForwards;bounce3.removedOnCompletion = NO;[self.ghost3.layer addAnimation:bounce3 forKey:@"position"]; }在viewDidLoad中調用下該方法.好的,這個時候我們可以跑一下我們的代碼了.如果一切無誤的話,你會看到整個迷宮界面,并且三個ghost在上下跳動,是不是有點意思了?
好了,下面就讓我們使用加速計來操縱我們的pacman.
第二步:使用加速計來移動pacman
一.聲明屬性
?iPhone的內置加速計為我們iOS開發人員提供了大量創造有趣游戲的機會.我們經常見到通過傾斜iPhone來控制游戲角色的app,比如狂野飛車等,當然那個就相當復雜了.現在我們通過這個簡單的迷宮游戲來熟悉下系統提供的加速計如何使用.首先我們需要聲明一些屬性.在此之前導入必要的頭文件
#import <CoreMotion/CoreMotion.h> @property (nonatomic,assign) CGPoint currentPoint; //pacman當前位置 @property (nonatomic,assign) CGPoint previousPoint; //pacman移動的位置 @property (nonatomic,assign) CGFloat pacmenXVelocity; //速度的x分量(速度是矢量的) @property (nonatomic,assign) CGFloat pacmanYVelocity; //速度的y分量(速度是矢量的) @property (nonatomic,assign) CGFloat angle; //pac當前角度 為了看起來更真實,我們設置pacman的旋轉 @property (nonatomic,assign) CMAcceleration acceleration; //加速度計測量出的當前加速度 @property (nonatomic,strong) CMMotionManager *motionManager; //是一個隊列,可以幫助我們接收和處理從加速度計發送的數據 @property (nonatomic,strong) NSOperationQueue *queue; @property (nonatomic,strong) NSDate *lastUpdateTime; //允許我們控制加速度計上次調用以來的時間額外---加速計(Accelerometer)的介紹
1-加速計簡介
-
1.加速計的作用
- 用于檢測設備的運動(比如搖晃)
- 檢測某一個方向上力的作用.它用來測量加速力,無論是由重力還是運動引起的加速力都可以.所以換言之,加速計可以測量移動速度并且能夠感知到它的握持角度.如果您還想了解下iPhone加速計如何工作的更多信息,可以查看此視頻.
- 用于檢測設備的運動(比如搖晃)
-
2.加速計的經典應用場景
- 搖一搖
- 計步器
-
3.加速計的原理
- 檢測設備在x軸、y軸、z軸上的加速度
- 哪一個方向有力的作用,哪一個方向就運動了
- 根據加速度的數值,就可以判斷出在各個方向上的作用力度
- 哪一個方向有力的作用,哪一個方向就運動了
- 檢測設備在x軸、y軸、z軸上的加速度
-
4.在iOS4之前加速度計是由UIAccelerometer類來負責采集數據,現在一般都是用CoreMotion來處理加速度
-
5.需要注意的是:加速計的坐標系不是iPhone屏幕的坐標系,而是大家在上學時期所熟知的笛卡爾坐標系
2-加速計的坐標
home鍵在下時,Y軸是笛卡爾坐標系。home建在右,X軸是笛卡爾坐標系。屏幕朝上時,z是笛卡爾坐標系.這里說的是iPhone朝地的方向,因為加速器是根據重力來檢測的.(具體很難講清楚,大家一會可以在項目中打印rotationRate.x , rotationRate.y, rotationRate.z來體驗下)
iOS Core Motion框架允許我們開發人員從設備硬件中獲取運動數據并且處理這些數據.這里的硬件設備包括加速計,陀螺儀,磁力計,計步器等.有興趣的童鞋可以私自去深挖其中的奧秘.其中CMMotionManager類負責管理運動數據.通過使用該類,我們可以定期獲取加速計檢測到的數據,我們待會將有用到.
了解了加速計的原理后,下面讓我們來繼續我們的迷宮游戲項目.
二.使用CMMotionManager來獲取運動數據
獲取運動數據會調用一個專門采樣的方法,這里我們來設定一下采樣的頻率.采樣頻率越高,那么pacman運動就會更精確,當然也會更耗電些.那么我們來設定一個值每秒鐘采樣60次吧,這樣pacman的運動看起來會相當平滑.我們為此定義一個宏.
#define kUpdateInterval (1.0f/60.f)下面讓我們來初始化加速計,設置pacman的運動
- (void)pacmanAnimation {self.lastUpdateTime = [NSDate date]; //記錄當前時間self.currentPoint = CGPointMake(0, 144); //設置pacman初始位置self.motionManager = [[CMMotionManager alloc] init]; //創建運動管理器self.queue = [[NSOperationQueue alloc] init]; //隊列一般盡量使用全局變量self.motionManager.accelerometerUpdateInterval = kUpdateInterval;//設置采樣間隔,每秒鐘60次,這樣,咱們的pacman應該運動起來足夠平滑了吧.//開始采樣的方法[self.motionManager startAccelerometerUpdatesToQueue:self.queue withHandler:^(CMAccelerometerData * _Nullable accelerometerData, NSError * _Nullable error) {//記錄下當前的加速度.有興趣的童鞋可以打印看看x,y,z值感受下.[self setAcceleration:accelerometerData.acceleration];// NSLog(@"x: %f, y: %f, z: %f", self.acceleration.x , self.acceleration.y, self.acceleration.z);//回到主線程更新UI[self performSelectorOnMainThread:@selector(updateLocation) withObject:nil waitUntilDone:NO];}]; }三.移動吃豆子
目前我們已經完成了不斷采樣pacman加速度的數據了,接下來我們將使用這些數據來不斷更新pacman的新位置.讓我們來實現updateLocation方法.
- (void)updateLocation {//獲取兩次采樣之間的時間間隔. 前面加個負號是為了保證值為正數.NSTimeInterval secondSinceLastDraw = - ([self.lastUpdateTime timeIntervalSinceNow]);//獲取y方向的速度分量.因為屏幕是橫屏,所以self.pacmanYVelocity要通過self.acceleration.x來進行疊加,這里童鞋們要注意的.又因為我們之前選擇橫屏的方向為:Landscape Right,所以是通過減號來保證y方向速度的增量,如果童鞋們當時選的是Landscape Left,那么就要改成加號了.所以大家一定要注意這個細節.具體原因跟上面提到的笛卡爾坐標系有很大關聯.self.pacmanYVelocity = self.pacmanYVelocity - (self.acceleration.x*secondSinceLastDraw);//x方向的速度分量的計算原理同上. 這樣計算的目的是實現在重力加速度作用下,實現速度越來越快的效果,更有現實感.self.pacmenXVelocity = self.pacmenXVelocity - (self.acceleration.y*secondSinceLastDraw);//上面得出每次采樣時當前的速度后,這里就可以計算出每兩次采樣時間間隔內pacman在x,y方向上挪動的距離.500是自己設置的一個參數.確定pacman移動的速度,這個值越大,pacman移動得越快.個人感覺設定這個值已經可以了.大家可以根據自己的喜好擅自更改CGFloat xDelta = secondSinceLastDraw*self.pacmenXVelocity*500;CGFloat yDelta = secondSinceLastDraw*self.pacmanYVelocity*500; //大伙們可以看看打印出來的結果進行分析. // NSLog(@"secondSinceLastDraw:%lf---x:%lf---pacmanYVelocity:%lf---yDelta:%lf",secondSinceLastDraw,self.acceleration.x,self.pacmanYVelocity,yDelta);//每次采樣后更新pacman的位置self.currentPoint = CGPointMake(self.currentPoint.x+xDelta, self.currentPoint.y+yDelta);//移動pacman[self movePacman];//pacman做旋轉操作[self rotateThePacman];//把當前的時間記錄下來作為上一次更新的時間self.lastUpdateTime = [NSDate date]; }上面更新pacman位置的方法我在注釋中已經描述的比較清楚了,這里就不再次描述.值得注意的是,因為速度是矢量值,所以我們要分別計算它的x矢量和y矢量,當然如果是3d的話是還存在z矢量的,因為我們這個是平面小游戲,就不考慮z矢量了.另外有童鞋可能會問:既然已經把采樣間隔時間設置為每秒60次了,還要再次計算兩次采樣之間的時間間隔呢?那是因為這只是一個近似值.出于某些原因,我們可能會不定期的獲取數據,所以我們還是計算自上次調用以來經過的時間較好,這樣比較準確.
最后,我們也要使pacman挪到相應的位置的.
- (void)movePacman {CGRect frame = self.pacman.frame;frame.origin.x = self.currentPoint.x;frame.origin.y = self.currentPoint.y;self.pacman.frame = frame;//保存最新的位置self.previousPoint = self.currentPoint; }到了這里,大家可以跑一跑代碼體驗下pacman隨著屏幕的搖晃而運動,是不是開始有點感覺了?但還是有一點不盡人意的地方,那就是pacman運動起來自己卻是靜態的且不切實際,我們希望看到他在迷宮中移動時也能保持旋轉.
下面讓我們來實現pacman的旋轉.
- (void)rotateThePacman {CGFloat newAngle = (self.pacmenXVelocity + self.pacmanYVelocity)*M_PI*4;self.angle += newAngle*kUpdateInterval;CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];rotate.fromValue = @(0);rotate.toValue = @(self.angle);rotate.duration = kUpdateInterval;rotate.repeatCount = 1;rotate.removedOnCompletion = NO;rotate.fillMode = kCAFillModeForwards;[self.pacman.layer addAnimation:rotate forKey:@"10"];self.lastAngle = self.angle; }角度的旋轉真的有點棘手,它涉及到一些數學知識.我目前還沒有想到很好的辦法,只能姑且通過self.angle += newAngle*kUpdateInterval使舊值向新值過渡,實現pacman的平滑旋轉.如果要想實現pacman的開口處始終朝著前進的方向,這需要花費點時間是琢磨計算公式,你們可以去嘗試一下.
現在可以跑跑代碼感受一下.
第三步:設置碰撞效果
以上情況,pacman可以穿透任何walls和ghosts還有屏幕等障礙物,還屬于半生不熟的狀態,現在我們來給pacman設置障礙,與障礙物接觸時產生碰撞效果.包括以下四種情況:
與屏幕邊界碰撞,pacman不離開屏幕,而是反彈;
當pacman繞過所有障礙并吃掉黃豆時,那么玩家就贏得比賽;
當與ghost碰撞時,則游戲失敗;
當和wall碰撞時,那么也是反彈.
一.與屏幕邊界的碰撞
這個比較容易,只要檢查pacman是否在屏幕內即可.
- (void)collisionWithBoundaries {if (self.currentPoint.x<0) {_currentPoint.x = 0;self.pacmenXVelocity = -(self.pacmenXVelocity/2.0);}if (self.currentPoint.y<0) {_currentPoint.y = 0;self.pacmanYVelocity = -(self.pacmanYVelocity/2.0);}if (self.currentPoint.x>self.view.bounds.size.width-self.pacman.bounds.size.width) {_currentPoint.x = self.view.bounds.size.width-self.pacman.bounds.size.width;//反轉速度矢量的方向,同時讓速度減半,模仿現實世界的效果self.pacmenXVelocity = -(self.pacmenXVelocity/2.0);}if (self.currentPoint.y>self.view.bounds.size.height-self.pacman.bounds.size.height) {_currentPoint.y = self.view.bounds.size.height-self.pacman.bounds.size.height;//反轉速度矢量的方向,同時讓速度減半,模仿現實世界的效果self.pacmanYVelocity = -(self.pacmanYVelocity/2.0);} }在movePacman方法的開頭處就調用此方法.
二.與exit的碰撞
撞到黃豆后,就提示恭喜贏得比賽.并且停止motionManager的運動采樣方法.
- (void)collisionWithExit {if (CGRectIntersectsRect(self.pacman.frame, self.exit.frame)) {//停止加速計采樣[self.motionManager stopAccelerometerUpdates];UIAlertController *ac = [UIAlertController alertControllerWithTitle:@"Congratulations" message:@"You've won the game!" preferredStyle:UIAlertControllerStyleAlert];UIAlertAction *action = [UIAlertAction actionWithTitle:@"ok" style:UIAlertActionStyleDefault handler:nil];[ac addAction:action];[self presentViewController:ac animated:YES completion:nil];} }Core Graphics提供了CGRectIntersectsRect來幫助我們檢查一個view的幀是否與另一個指定view的幀重疊.
三.與ghost的碰撞
撞到ghost后,那么就提示游戲失敗,并且回到起點.
- (void)collisionWithGhosts {CALayer *ghostLayer1 = self.ghost1.layer.presentationLayer;CALayer *ghostLayer2 = self.ghost2.layer.presentationLayer;CALayer *ghostLayer3 = self.ghost3.layer.presentationLayer;if (CGRectIntersectsRect(self.pacman.frame, ghostLayer1.frame)||CGRectIntersectsRect(self.pacman.frame, ghostLayer2.frame)||CGRectIntersectsRect(self.pacman.frame, ghostLayer3.frame)) {UIAlertController *ac = [UIAlertController alertControllerWithTitle:@"Oops" message:@"Mission Failed" preferredStyle:UIAlertControllerStyleAlert];UIAlertAction *action = [UIAlertAction actionWithTitle:@"ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {self.currentPoint = CGPointMake(0, 144);}];[ac addAction:action];[self presentViewController:ac animated:YES completion:nil];} }四.與walls的碰撞
這個可能比前面幾個碰撞稍微復雜一點點,因為我們這里的wall是方形的,所以回涉及到時撞墻的左右還是上下,那么加速度的方向將有不同的取擇.
- (void)collisionWithWalls {CGRect frame = self.pacman.frame;frame.origin.x = self.currentPoint.x;frame.origin.y = self.currentPoint.y;for (UIImageView *image in self.wall) {if (CGRectIntersectsRect(frame, image.frame)) {CGPoint pacmanCenter = CGPointMake(frame.origin.x+frame.size.width/2.f, frame.origin.y+frame.size.height/2.f);CGPoint imageCenter = CGPointMake(image.frame.origin.x+frame.size.width/2.f, image.frame.origin.y+frame.size.height/2.f);CGFloat angleX = pacmanCenter.x - imageCenter.x;CGFloat angleY = pacmanCenter.y - imageCenter.y;//判斷x分量和y分量的大小,如果x>y,則說明撞到了wall的左右邊緣,那么就將速度的x分量反轉并減半;如果x<y,說明撞到了wall的上下邊緣,那么就將速度的y分量反轉并減半.if (fabs(angleX)>fabs(angleY)) {_currentPoint.x = self.previousPoint.x;self.pacmenXVelocity = -self.pacmenXVelocity/2.f;}else {_currentPoint.y = self.previousPoint.y;self.pacmanYVelocity = -self.pacmanYVelocity/2.f;}}} }將這幾個方法在movePacman方法中調用.
- (void)movePacman {[self collisionWithBoundaries];[self collisionWithExit];[self collisionWithGhosts];[self collisionWithWalls];CGRect frame = self.pacman.frame;frame.origin.x = self.currentPoint.x;frame.origin.y = self.currentPoint.y;self.pacman.frame = frame;self.previousPoint = self.currentPoint; }到了這一步了,那么就恭喜你一款簡易的迷宮游戲完成了.跑下代碼體驗一下吧.當然了這個游戲很不完美,存在著很多錯誤,其實這個小項目的目的是來初步的了解iOS系統自帶的速度計的使用.大家相互學習吧.歡迎你們可以在此基礎上制作出一個更高級別的游戲.
總結
以上是生活随笔為你收集整理的加速计简单使用---迷宫游戏的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android 中使用地图加载wms服务
- 下一篇: 快速了解必要的网络知识