作者:Marin Todorov
你是一名想着创造一款iOS游戏,但却没有任何编程经验的资深iOS开发者吗?也许学习OpenGLES会让你感到不安,或者你从未使用过Cocos2d在iPhone上开发游戏?
而现在我将告诉你一个好消息,即UIKit将成为所有游戏开发者都可以使用的强大工具。他的高端框架将帮助你创造所有游戏逻辑—-不管是创造动画(注:包括框架动画)还是播放音频和视频。
这三部分教程将引导着你面向iPad创造一款桌面游戏。你将在此学习创造一款稳定且标准的iOS应用的最佳方法。
这系列教程是针对于那些熟悉Objective-C的开发者,因此不会过多阐述像原语类型或语法等细节。
开始:引入重组字你将在这篇教程中使用UIKit所创造的文字游戏是关于重组字的内容。
重组字是关于重新排列某些文字或短语的字母而组成一个新单词或短语。例如,单词cinema可以重新编排而成为iceman。你将让玩家根据所提供的单词和短语而创造重组字。
完成后的游戏将如下:
finished game
就像我们所看到的,在屏幕最底端有一排字母,便是它们构成了最初的短语。从上面的截图中我们可以看出是少了字母M的admirer,因为玩家已经将M拖到上列中了。
玩家的任务便是使用admirer中的字母组合成不同的单词。
你能猜到是什么吗?我已经给予暗示了,即首个字母是M!
不管怎样,这便是你将创造的一款游戏。当你在创造这款游戏时,你将学到以下基本内容:
适当的控制器(MVC)游戏结构。
从配置文件中加载关卡配置。
组织你的游戏控制器逻辑。
基于AV Foundation为UIKit编写简单的音频控制。
使用QuartzCore创造静态视觉效果。
使用UIAnimation在游戏内部顺畅地移动。
使用UIKit粒子去添加视觉效果。
单独创建HUD和游戏层。
使用游戏字体去完善外观。
将默认的UIKit组件升级为游戏般的组件。
同时也包括:我在创造游戏时所使用的软件工具。
让我们进行深入分析!
启动项目为了使用UIKit去创造iOS游戏,本篇教程将提供一个包含启动XCode项目以及所有游戏资产(游戏邦注:包括图像,字体,音频和配置文件)的ZIP文件。
下载重组字第一部分的启动文件,解压并在Xcode中打开它。
着眼于启动项目中包含了哪些内容。左边的项目文件浏览器应该如下:
starter project
以下是关于你所看到的内容的总结:
config.h:放在一起的代码配置。
Levels:包含3个.plist文件的文件夹,用于定义游戏的3个难度关卡。
Classes/models:你将在此添加数据模式。
Classes/views:你将在此创造控制器。
Assets/Fonts:对于游戏HUD的定制TTF字体。
Assets/Particles:这里有一个PNG文件能够用于创造粒子效果。
Assets/Audio:创意共享许可音频文件。
Assets/Images:这是你将用于创造游戏的所有图像。
MainStoryboard.storyboard:打开故事板你将看到那里只有一个带有背景的屏幕,并且只有一张模糊的木纹图像,这同时也包含于项目资产中。
credits.txt:包含项目中所使用的所有资产来源的链接。
播放声音并浏览图像并运行项目。
run project
这比空空的应用窗口好多了吧?
在本系列教程的第一部分中,你的目标便是在屏幕上呈现最初的重组字与目标盒。在今后的教程中,你还将添加能力去拖动文件与游戏逻辑。
在这里你的任务将被分为8个小步骤:
1.加载关卡配置文件。
2.创造游戏控制器类。
3.创造屏幕视图。
4.创造组块视图。
5.处理屏幕上的组块。
6.添加字母。
7.轻微且随机地旋转字母。
8.添加目标。
让我们分析这些步骤。
1)加载关卡配置文件在项目文件浏览器中选择level.plist文件。着眼于里面所包含的内容:
这里有3个关键元素:
pointsPerTile:对于每个正确放置的组块的奖励点。
timeToSolve:玩家解决谜题所花费的时间。
anagrams:一列重组字,每个重组字包含2个条款,即最初短语和最终短语。
让我们暂时先专注于anagrams。这是一列数组元素,每个元素拥有两串元素。你将创造一个数据模型在Objective-C中加载这一配置。
在Anagrams/Classes/models文件夹中创造一个全新Objective-C类文件。调用全新类Level,并将其设置为NSObject的子类。
打开Level.h,并在@interface和@end行中间添加如下属性和方法:
//properties stored in a .plist file
@property (assign, nonatomic) int pointsPerTile;
@property (assign, nonatomic) int timeToSolve;
@property (strong, nonatomic) NSArray* anagrams;
//factory method to load a .plist file and initialize the model
+(instancetype)levelWithNum:(int)levelNum;
这一属性符合.plist文件的结构,所以你只需要加载数据并填入属性便可。你同样也需要提出一个工厂方法为特定的难度关卡创造Level类。其范围将从1到3,即从最简单到最复杂。
现在,添加代码去提高Level数据模式类。在Level.m的类执行中,添加工厂方法的方法体去创造Level实体:
+(instancetype)levelWithNum:(int)levelNum;
{
//1 find .plist file for this level
NSString* fileName = [NSString stringWithFormat:@"level%i.plist", levelNum];
NSString* levelPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:fileName];
//2 load .plist file
NSDictionary* levelDict = [NSDictionary dictionaryWithContentsOfFile:levelPath];
//3 validation
NSAssert(levelDict, @”level config file not found”);
//4 create Level instance
Level* l = [[Level alloc] init];
//5 initialize the object from the dictionary
l.pointsPerTile = [levelDict[@"pointsPerTile"] intValue];
l.anagrams = levelDict[@"anagrams"];
l.timeToSolve = [levelDict[@"timeToSolve"] intValue];
return l;
}
在方法体中有4个小环节—-即四个朝向方法目标的步骤:
1.基于传达到方法中的LevelNum值而决定通向关卡配置文件(level1.plist,level2.plist等等)的道路。
2.将文件内容加载到NSDictionary中。幸运的是,NSDictionary类知道如何分析.plist文件!
3.验证levelDict中所加载的某些内容。我经常使用NSAssert去寻找错误(如果这种情况曾出现在开发过程中,我便希望能够停止应用的开发,从而让我能够尽早解决这些错误。)
4.创造一个全新的Level实体。
5.从NSDictionary中复制值到Level对象属性中。
现在让我们尝试以下类,并验证你的第一步是否达到了预期目标!
打开ViewController并在最上方添加如下代码:
#import “Level.h”
在viewDidLoad最后添加:
Level* level1 = [Level levelWithNum:1];
NSLog(@”anagrams: %@”, level1.anagrams);
在Xcode主机只能够你将看到Level 1的一列重组字。
Level
现在你便可以将NSLog行从viewDidLoad中删除以保持主机的整洁。
2)创造一个游戏控制器类那些未接触过MVC便直接跳到iOS开发的人将会对控制器和UIViewController感到困惑。
控制器是一个专注于应用逻辑某些部分的类,区别于应用数据和呈现这些数据所使用的类。
子类UIViewController只是一个管理特殊UIView描述的控制器。(UIViews用于在屏幕上呈现某些内容。)
在本篇教程中,你将创造一个控制器类去管理游戏逻辑。让我们开始进行尝试。
首先在Anagrams/Classes/controllers中创造一个全新的Objective-C类文件。调用全新的类GameController并将其设置为NSObject的子类。
打开GameController.h并添加如下代码:
#import “Level.h”
然后在GameController界面添加如下:
//the view to add game elements to
@property (weak, nonatomic) UIView* gameView;
//the current level
@property (strong, nonatomic) Level* level;
//display a new anagram on the screen
-(void)dealRandomAnagram;
我打赌你已经发现当前目标的构建模块了:
gameView:你将用于在屏幕上呈现游戏元素的视图。
Level:为当前游戏关卡储存重组字和其它设置的Level对象。
dealRandomAnagram:你将调用这一方法在屏幕上呈现当前的重组字。
到目前为止的代码还非常简单。让我们转到GameController.m并添加dealRandomAnagram的占位符版本:
//fetches a random anagram, deals the letter tiles and creates the targets
-(void)dealRandomAnagram
{
//1
NSAssert(self.level.anagrams, @”no level loaded”);
//2 random anagram
int randomIndex = arc4random()%[self.level.anagrams count];
NSArray* anaPair = self.level.anagrams[ randomIndex ];
//3
NSString* anagram1 = anaPair[0];
NSString* anagram2 = anaPair[1];
//4
int ana1len = [anagram1 length];
int ana2len = [anagram2 length];
//5
NSLog(@”phrase1[%i]: %@”, ana1len, anagram1);
NSLog(@”phrase2[%i]: %@”, ana2len, anagram2);
}
这是关于上述代码的解释:1.你需要确保这一方法只有在设置了Level属性后才能调用,并且它的Level对象包含了重组字。
2.你生成了一个随机索引并将其整合到重组字列表中,然后根据这一索引抓取重组字。
3.你在anagram1和anagram2中储存了2个短语。
4.然后你在每个短语中将字符数储存到ana1len和ana2len中。你将在之后进一步处理这一信息。
5.最后,你将在主机中呈现这些短语。这需要经历测试。
现在你的应用已经能够在一开始加载故事板MainStoryboard.storyboard。这一故事板使用ViewController类创造了一个屏幕。
因为你的游戏只能拥有一个屏幕,所以你将在ViewController的init方法中添加所有初始化内容。对于包含一个以上视图控制器的游戏,你便可能想要将初始化内容放置在其它地方,如AppDelegate类中。
打开ViewController.m,并在最上方添加如下:
#import “GameController.h”
放置私有类界面:
@interface ViewController ()
@property (strong, nonatomic) GameController* controller;
@end
最后,在执行中添加新的初始化方法:
-(instancetype)initWithCoder:(NSCoder *)decoder
{
self = [super initWithCoder:decoder];
if (self != nil) {
//create the game controller
self.controller = [[GameController alloc] init];
}
return self;
}
上述方法initWithCoder:当应用从故事板中初始化时将会自动调用。
现在你创造了一个GameController类,并在ViewController执行中拥有一个实体,现在是时候在屏幕上设置一些字母组块了。
3)创造一个屏幕视图下一步很简单:你需要在屏幕上创造一个视图,并将其与GameController连接在一起。
打开config.h并着眼于文件中的前两个定义:KScreenWidth和kScreenHeight。这是两个快捷方式,也能够在Landscape模式中处理屏幕的交换大小。你将使用这些定义去创造游戏视图。
回到ViewController.m并在viewDidLoad最后添加如下代码:
//add one layer for all game elements
UIView* gameLayer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight)];
[self.view addSubview: gameLayer];
self.controller.gameView = gameLayer;
在上述代码块中,你创造了一个基于屏幕尺寸的新视图,并将其添加到ViewController的视图中。然后你将其分配到GameController实体的gameView属性中。基于这一方法,GameController将使用这一视图去处理所有游戏元素(除了HUD)。
现在你已经创造了模式和控制类,并且你也已经为重组字游戏创造了第一个定制视图类。
4)创造组块视图在这一部分,你将创造一个视图去呈现如下组块:
view
现在,你将专注于创造带有背景图像的放心组块。之后你将在上面添加字母。
在文件租Anagram/Classes/view中创造一个名为TileView的新类,并将其设置为UIImageView的子类。这是一个好的开始—-UIImageView类将提供呈现图像的意义,所以你只需要在图像上方添加标签即可。
打开TileView.h并在界面上添加如下代码:
@property (strong, nonatomic, readonly) NSString* letter;
@property (assign, nonatomic) BOOL isMatched;
-(instancetype)initWithLetter:(NSString*)letter andSideLength:(float)sideLength;
以下是关于你刚刚所创造的内容:
letter:将控制分配到组块上的字母的属性。
isMatched:控制Boolean去预示某一组快是否成功“匹配”屏幕上方目标的属性。
initWithLetter:andSideLength:一个定制init方法,能够设置带有特定字母和组块大小的类实体。
你可能会好奇为什么需要设置组块的大小?
让我们着眼于下面两种设置:
setup
第一种设置呈现的是一个短字,所以要求更大的组块去填充屏幕。第二种设置呈现的是一个短语,要求增加组块数量,所以便需要缩小组块。你可以从中看到为什么组块需要设置一个合理的尺寸,如此你便可以根据屏幕上呈现的组块数而设置不同大小的组块。
你的控制器将估算短语的长度,根据字符数去分割屏幕宽度并决定组块大小。你将快速执行这一步,但首先你需要执行组块的代码。
转换到TileView.并用以下基本类架构换置里面的内容:
#import “TileView.h”
#import “config.h”
@implementation TileView
//1
- (id)initWithFrame:(CGRect)frame
{
NSAssert(NO, @”Use initWithLetter:andSideLength instead”);
return nil;
}
//2 create new tile for a given letter
-(instancetype)initWithLetter:(NSString*)letter andSideLength:(float)sideLength
{
//the tile background
UIImage* img = [UIImage imageNamed:@"tile.png"];
//create a new object
self = [super initWithImage:img];
if (self != nil) {
//3 resize the tile
float scale = sideLength/img.size.width;
self.frame = CGRectMake(0,0,img.size.width*scale, img.size.height*scale);
//more initialization here
}
return self;
}
@end
这是一个较长的代码块,但是如果你仔细看的话便会发现,它只是关于定义占位符,所以你可以在之后创建组块的功能。在代码中有一些不同的环节,让我们在此进行分析:
1.你覆盖了initWithFrame:因为你已经拥有一个定制初始化方法,你不再需要调用initWithFrame:。相反的,你添加了一个每次都会衰败的NSAssert。这意味着你拥有足够的机会去创造一个可进行初始化的组块。
在一个小小应用中,这只是保护你自己的方法。但是在一个大型应用中,或者当你身处一个团队时,这便是一种预防措施能够通过阻止任何人调用这一方法而减少犯错的机率。(为了真正保护自己,你需要为如下每个方法创造类似的执行:init, initWithCoder:, initWithImage: and initWithImage:highlightedImage:。)
2.initWithLetter:andSideLength:是你的定制方法,能够初始化组块实体。它加载了tile.png图像并通过在父类(在这里便是UIImageView)上调用initWithImage:而创造了TileView实体。
3.然后你需要根据默认组块的大小进行计算,从而明确一个适合特定单词或短语的面板大小。为此,你只需要调整TileView的frame便可。UIImageView能够自动调整其图像去匹配框架大小。
这时候的代码已经能够呈现出默认的空白组块图像了。现在你需要执行更多游戏控制功能,从而才能看到一些空白组块排列在屏幕上。
5)处理屏幕上的组块转换到GameController.m—-你的dealRandomAnagram方法已经加载了所需要的数据,但却还不够多。是时候去处理这种情况了!
在GameController.m的上方添加如下代码:
#import “config.h”
#import “TileView.h”
现在你便能够访问TileView类以及你储存在config.h中的任何共享设置。
在@implementation环节添加如下私有变量:
@implementation GameController
{
//tile lists
NSMutableArray* _tiles;
NSMutableArray* _targets;
}
如何解释这些变量?
_tiles是一个数组,控制着TileViews在屏幕下方呈现的组块。这些组块包含重组字的最初短语。
_targets是一个数组,控制着目标视图在屏幕最下方呈现的空间,玩家将在此拖曳组块去形成重组字的第二个短语。
继续前进前,你还需要在组块间设置一个常数。跳到config.h并添加如下定义:
#define kTileMargin 20
有些人会反驳道:“难道我不能只是用20?”这是个很有趣的点,但是我们之所以要在配置文件中进行预定义是有原因的。
根据以往的经验,当你可以开始挑战游戏玩法时,你便需要去调整代码中的任何数字。最佳实践便是具体化配置文件中的所有数字,并赋予这些变量有意义的名称。这一做法将能带给你很大的帮助,特别是当你致力于团队工作并需要将游戏递交给设计师进行调整时。
让我们回到GameController.m,准备在dealRandomAnagram最后添加一些代码。你在两个短语中都拥有一些字符,所以你能够很轻松地估算所需要的组块大小。在方法最后添加如下代码:
//calculate the tile size
float tileSide = ceilf( kScreenWidth*0.9 / (float)MAX(ana1len, ana2len) ) – kTileMargin;
你占据了90%屏幕宽度,并将其以字符数划分开来。你使用的是拥有更多字符数的短语—-需要牢记的是空间的数值总是会不断变化!你通过kTileMargin而有效设置了组块间的距离。
接下来需要找到第一个组块的最初x位置。这取决于单词的长度以及你所计算的组块大小。再一次,你需要在dealRandomAnagram最后添加如下内容:
//get the left margin for first tile
float xOffset = (kScreenWidth – MAX(ana1len, ana2len) * (tileSide + kTileMargin))/2;
//adjust for tile center (instead of the tile’s origin)
xOffset += tileSide/2;
通过将屏幕宽度减去单词组块的宽度,你便能够确定第一个组块在屏幕上的位置。如果存在一种更简单的“居中”组块的方法该多好!
据需在相同的方法中添加如下代码去呈现组块:
//1 initialize tile list
_tiles = [NSMutableArray arrayWithCapacity: ana1len];
//2 create tiles
for (int i=0;i<ana1len;i++) {
NSString* letter = [anagram1 substringWithRange:NSMakeRange(i, 1)];
//3
if (![letter isEqualToString:@" "]) {
TileView* tile = [[TileView alloc] initWithLetter:letter andSideLength:tileSide];
tile.center = CGPointMake(xOffset + i*(tileSide + kTileMargin), kScreenHeight/4*3);
//4
[self.gameView addSubview:tile];
[_tiles addObject: tile];
}
}
让我们分析上述代码:
1.首先你初始化了_tiles可变数组的最新副本,确保它足够大能容纳最初短语中的所有字母。在一开始面向明确的数组非常重要,因为这一方法将在应用的运行过程中被多次调用。因为空间被略过了,所以如果你未能明确数组,那么便会遗留下一些来自最初短语的组块。
2.然后你需要循环通过短语并为每个字符创造全新的NSString。将其保存为letter。
3.你检查了每个字母。你创造了一款全新组块并使用xOffset,tileSide和kTileMargin对其进行设置。在此你需要根据字母在短语中的位置去估算位置(以i进行表示)。所以如果是5,这一算式将估算出5个组块所占据的空间并会在空间右边添加新组块。
你将在屏幕高度的四分之三处垂直排列组块。
4.最后,你在gameView和_tiles数组中添加了组块。首先在屏幕上呈现了组块,这能帮助你在之后加速组块的循环。
是时候进行最后的接触了—-你需要加载关卡数据并调用dealRandomAnagram。转换到ViewController.m并在viewDiaLoad最后添加这两行代码:
self.controller.level = level1;
[self.controller dealRandomAnagram];
记得你已经拥有一个Level项目(游戏邦注:你在这个方法最上方创造了它),所以你只需要将其传送到GameController并调用dealRandomAnagram便可。
现在你需要通过运行去检查结果!你的模拟器应该如下:
simulator
多生动啊!
注:你的视图可能会因为应用每次运行选择随机重组字的不同而与上图有所差异。如此你便可以基于不同短语多运行几次。
6)添加字母转换到TileView.m。在此你将添加字母到组块中。找到initWithLetter:andSideLength中的评论“//more initialization here”,并在它下方添加如下代码:
//add a letter on top
UILabel* lblChar = [[UILabel alloc] initWithFrame:self.bounds];
lblChar.textAlignment = NSTextAlignmentCenter;
lblChar.textColor = [UIColor whiteColor];
lblChar.backgroundColor = [UIColor clearColor];
lblChar.text = [letter uppercaseString];
lblChar.font = [UIFont fontWithName:@"Verdana-Bold" size:78.0*scale];
[self addSubview: lblChar];
上述代码创造了一个新的UILabel,将它添加到组块视图中。因为你希望字母能够一目了然,所以你创造了一个与组块一样大的标签。然后你将字母排列在一起并呈现在组块中。
你通过调用[letter uppercaseString]而确保字母均是大写。使用大写字母能够让降低游戏玩法的难度,因为玩家能够更轻松地意识到它们,特别是小孩(这便是你们游戏的目标用户)。
最后,你在此使用了之前所估算的比例引子。对于原尺寸的组块来说,78pts的Verdana字体最合适了,但是对于其它尺寸,你则需要相应地调整字体的大小。
在initWithLetter:andSideLength最后添加如下初始化代码:
//begin in unmatched state
self.isMatched = NO;
//save the letter
_letter = letter;
上述代码将组块的isMatched标志设为NO,即预示着组块并不匹配屏幕上方的目标。它同时也将字母保存到组块的letter属性中。
创建并运行项目。就像我所说的,现在的你正朝着目标靠近。做得好!
run project
再一次地,你的屏幕可能与上图有所差异。但这意味着你的游戏控制器正在执行预期任务并处理随机重组字。
再次着眼于上图,你会发现现在的组块就像是被不会思考的机器人放置在面板上一样—-整齐得让人别扭。所以现在你需要添加一些随机性。
7)轻微且随机地旋转字母随机性是创造一款成功的计算机游戏的关键。你总是想要尽可能地随机化游戏。举个例子来说吧,没有玩家喜欢面对永远都做着同样事情的敌人。
在重组字游戏中,你可以随机放置每个组块,从而让它们能够更自然地呈现于面板上,就像人们未可以对齐而进行放置一样。
random
为了实现这种随机性,你需要在TileView.h的界面上添加如下方法:
-(void)randomize;
在TileView.中添加如下randomize执行:
-(void)randomize
{
//1
//set random rotation of the tile
//anywhere between -0.2 and 0.3 radians
float rotation = randomf(0,50) / (float)100 – 0.2;
self.transform = CGAffineTransformMakeRotation( rotation );
//2
//move randomly upwards
int yOffset = (arc4random() % 10) – 10;
self.center = CGPointMake(self.center.x, self.center.y + yOffset);
}
这一代码主要在执行两个任务:
1.它在-0.2和0.3弧度间生成了一个随机角度(使用config.h中所定义的辅助功能randomf)。然后调用了CGAffineTransformMakeRotation为视图创造了一全新转换,即将基于随机角度围绕着中心进行旋转。
2.它在-10和0之间生成了一个随机值,并基于这种偏移量移动组块。
注:你可能想要用config.h中定义的常数去替换所有的这些数字,从而更轻松地调整组块外观。
现在你只需要调用这一方法。在GameController.m中通过在tile.center= …之后添加如下代码而编辑dealRandomAnagram:
[tile randomize];
再次运行,你便能够看到面板上随机放置的组块:
run
使用你所创造的内容,你也能够在游戏面板上快速添加目标。
8)添加目标目标视图与组块视图相类似。它们也将UIImageView划为子类并呈现了默认图像,它们同时也根据字母而进行了分配。而区别就在于,它们并未呈现出带有字母的标签,并且不能四处拖动。这便意味着你在设置目标视图所需要的代码与之前的类似—-这就简单多了!
在Anagrams/Classes/views中创造一个新类并调用TargetView,将其设置为子类UIImageView。
打开刚刚创造的TargetView.h,并在界面声明中添加如下属性和方法:
@property (strong, nonatomic, readonly) NSString* letter;
@property (assign, nonatomic) BOOL isMatched;
-(instancetype)initWithLetter:(NSString*)letter andSideLength:(float)sideLength;
是的,这与你之前用于TileView的属性和方法名称一样。就像你所看到的,现在你可以快速移动了。你已经知道上述代码的作用是什么了,所以你可以直接执行。
转换到TargetView.m并以如下代码替换假设方法:
- (id)initWithFrame:(CGRect)frame
{
NSAssert(NO, @”Use initWithLetter:andSideLength instead”);
return nil;
}
//create a new target, store what letter should it match to
-(instancetype)initWithLetter:(NSString*)letter andSideLength:(float)sideLength
{
UIImage* img = [UIImage imageNamed:@"slot"];
self = [super initWithImage: img];
if (self != nil) {
//initialization
self.isMatched = NO;
float scale = sideLength/img.size.width;
self.frame = CGRectMake(0,0,img.size.width*scale, img.size.height*scale);
_letter = letter;
}
return self;
}
再一次,这与你在TileView.m中所使用的代码也一样。你根据假设的边长重新调整了图像并分配字母。
现在你可以在屏幕上呈现图像了。打开GameController.m并添加如下代码:
#import “TargetView.h”
然后在dealRandomAnagram的评论“//initialize tile list”前添加如下代码:
// initialize target list
_targets = [NSMutableArray arrayWithCapacity: ana2len];
// create targets
for (int i=0;i<ana2len;i++) {
NSString* letter = [anagram2 substringWithRange:NSMakeRange(i, 1)];
if (![letter isEqualToString:@" "]) {
TargetView* target = [[TargetView alloc] initWithLetter:letter andSideLength:tileSide];
target.center = CGPointMake(xOffset + i*(tileSide + kTileMargin), kScreenHeight/4);
[self.gameView addSubview:target];
[_targets addObject: target];
}
}
这与你在处理面板上的组块的代码类似,除了这次你是从anagram2中的短语抓取字母并且并未随机排列它们的位置之外。你希望目标视图能够非常整洁。
为什么目标初始化必须出现在组块初始化前?因为你需要将子视图添加到父视图上,所以你先添加了目标视图才能确保它们最终是出现在组块之后。
如果你想要在组块视图后添加目标视图,你还可以采取其它方法让它们出现在组块后,如UIView的sendSubviewToBack:方法。但是先添加它们才是最有效的解决方法。
再次运行,你将看到面板设置即将完成了!
run again
等等!这两个短语并不匹配。在下方分别有3个组块紧跟着4个组块,而上方则是4个组块紧跟着3个组块!
不要担心,这是很正常的现象。毕竟包含两个短语的重组字可能拥有不同的字数。只要整体的字符数是一样的便可以。
恭喜你,现在你便已经创造了一个很棒的基础了。而在接下来的教程中你将开始与玩家互动并创造游戏玩法。