如何使用spritekit scenekit Kit制作像切绳子这样的游戏

课程名称读取中
支付宝支付
由百度开放云提供技术支持
&学院APP&&
扫描微信二维码精彩活动、课程更新抢先知
下载客户端,离线视频任您学
1.&Sprite Kit游戏引擎简介
2.&在XCode6中创建、编译和运行OS X游戏工程
3.&在XCode6中创建、编译和运行iOS游戏工程
4.&从零开始编写游戏程序:显示背景图
5.&手指触摸屏幕定位小飞鱼
6.&用手指移动小飞鱼
7.&旋转的飞鱼
8.&使用动作缩放飞鱼
9.&使用调度方法update缩放飞鱼
10.&SpriteKit动作详解(1):立即动作
11.&SpriteKit动作详解(2):移动动作
12.&SpriteKit动作详解(3):旋转动作
13.&SpriteKit动作详解(4):缩放动作
14.&SpriteKit动作详解(5):重置尺寸动作
15.&SpriteKit动作详解(6):淡入淡出和透明度动作
16.&SpriteKit动作详解(7):颜色混合动作
17.&SpriteKit动作详解(8): 逆序与重复执行动作
18.&SpriteKit动作详解(9):播放帧动画的动作
19.&SpriteKit动作详解(10):执行块动作
20.&SpriteKit动作详解(11):定制动作
21.&SpriteKit动作详解(12):串行和并行动作
22.&用九宫格缩放图像
23.&混合颜色
24.&粒子特效
25.&物理引擎简介
26.&弹跳的小球(模拟小球和地面的碰撞)
加入购物车
【课程类型】技术教程
【难度级别】高级
【适合人群】所有人
【课程介绍】 SpriteKit是Apple新推出的2D游戏引擎,与Cocos2d-x类似。可以使用Objective-C和Swift语言开发。可开发iOS和Mac OS X下的2D游戏。本课程主要介绍了SpriteKit的常用技术,包括基础知识、action、粒子系统、物理引擎等。
【课程目标】 本课程建立在Swift课程的基础上,让学员学会如何使用Swift语言开发基于iOS和Mac OS X的2D游戏。并通过大量的demo让读者重复掌握SpriteKit的各种使用技巧。
【课程计划】 本课程从SpriteKit的基础讲起,一直到Action、粒子系统以及物理引擎的应用。
全部评价(0)
34课程413951学员
参考知识库
为您推荐课程
讲师:李宁 23课时
讲师:李宁 30课时
讲师:李宁 25课时
讲师:李宁 14课时用 SpriteKit 做一个逃逸游戏 (4)
在代码中怎样使用?首先,为不同的物体类别创建常量。在 MyScene.m 中的常量声明语句后加入下列语句:
static const
uint32_t ballCategory
static const
uint32_t bottomCategory = 0x1 << 1;
static const
uint32_t blockCategory = 0x1 << 2;
static const
uint32_t paddleCategory = 0x1 << 3;
上述代码定义了 4 种物体类型。首先将字节(32位)的末位设置为1,其他位设置为0。然后通过左移操作符<< 将 1 不停往左移。这样,每个类型常量都只有一个位被置为1,对于每一个常量来说,它的 1 的位置都是不同的。目前你只用到两个类别,地(屏幕底部)和小球。不久后你会用到其他类别,如果必要,你还可以扩展更多的类别常量。定义好类别常量后,你可以创建一个物体,让它围在屏幕的底部。尝试独立完成这个任务,这和我们曾经创建过的围住屏幕四周的笼子是一样的。(将该物体的节点命名为 bottom,最终我们再来配置这个节点)
隐含内容:创建一个edge-based 状物体盖在屏幕底部
在 MyScene.m 的 initWithSize: 方法中加入:
CGRect bottomRect =
CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width,
SKNode* bottom = [SKNode
bottom.physicsBody =
[SKPhysicsBody bodyWithEdgeLoopFromRect:bottomRect];
[self addChild:bottom]; 万事俱备,只欠东风。让我们开始第一次亲密接触。首先,在 initWithSize 方法中设置地面、球和球拍的 categoryBitMasks。
bottom.physicsBody.categoryBitMask =
ball.physicsBody.categoryBitMask =
paddle.physicsBody.categoryBitMask
= paddleC 代码很简单。将你早先创建的常量赋给相应物体的categoryBitMask 掩码。接着,用以下代码( initWithSize: 方法中)设置contactTestBitMask 掩码:
ball.physicsBody.contactTestBitMask =
bottomC 这里,我们仅仅关心什么时候球和地面发生接触。因此将 contactTestBitMask设置为 bottomCategory。然后创建一个 SKPhysicsContactDelegate。由于这只是一个简单游戏,可以用 MyScene 作为所有接触的委托。在 MyScene.h 中找到:
@interface MyScene : SKScene 一句,修改为:
@interface MyScene : SKScene 采用比较正式的描述:MyScene 是一个SKPhysicsContactDelegate ,它将接受所有(配置好的)物体的碰撞通知!现在将 physicsWorld 的 delegate 设置为 MyScene。在MyScene.m 的 initWithSize: 方法中加入:
self.physicsWorld.contactDelegate = 设置SKPhysicsContactDelegate最后,实现 didBeginContact: 方法处理碰撞。在 MyScene.m中增加方法:
-(void)didBeginContact:(SKPhysicsContact*)contact
// 1 创建两个物体的局部变量
SKPhysicsBody* firstB
SKPhysicsBody* secondB
// 2 始终把类别代码较小的物体赋给firstBody变量
if (contact.bodyA.categoryBitMask
< contact.bodyB.categoryBitMask) {
firstBody = contact.bodyA;
secondBody = contact.bodyB;
firstBody =
contact.bodyB;
secondBody =
contact.bodyA;
// 3 对球和地接触的碰撞进行处理
if (firstBody.categoryBitMask == ballCategory &&
secondBody.categoryBitMask ==
bottomCategory) {
//TODO: 将打印语句替换为游戏结束界面
NSLog(@"Hit
bottom. First contact has been made.");
} 这段代码可能有点绕,让我们来过一遍代码。创建两个局部变量,用于指向碰撞发生时所涉及到的两个物体。判断两个物体其中哪一个的
categoryBitmask 值更小,并将它们赋给两个局部变量,其中 categoryBitmask 较小的一个始终要赋给
firstBody 变量(实际上是对二者进行排序)。当两个指定物体间的碰撞进行处理时,这样做(排序)会节省一些工作。由于前面排序过的缘故,现在你只需判断 firstBody 是否为球,secondBody 是否为地即可知道二者间是否发生碰撞。因为很显然,这种情况是不存在的:
firstBody 是地而 secondBody 是球(我们在定义常量时,ballCategory>bottomCategory)。然后简单输出一个打印语句。让我们来看看效果。编译运行程序,当球拍没有接住小球而让球落到了地上,则控制台中会输出消息:第一次亲密接触:)vcD48cD48c3Ryb25nPrS0vajTzs+3veHK+L3nw+Y8L3N0cm9uZz48L3A+PHA+utyyu9DSo6y1sc3mvNLK5LX0087Pt8qxsru/ycTcv7S1vb/Y1sbMqM/7z6Kho9LytMujrMTj0OjSqtPD0ru49s280M67r7XEvefD5sC0uObL383mvNKho9Xi0fmjrM7Sw8fQ6NKq16jDxc6q087Pt73hyvi0tL2o0ru49rOhvrChozwvcD48cD6147v3ssu1pSA8ZW0+RmlsZVxOZXdcRmlsZaGtPC9lbT6jrNGh1PE8ZW0+aU9TXENvY29hVG91Y2hcT2JqZWN0aXZlLUMgY2xhc3M8L2VtPiDEo7DlLLXju/c8ZW0+TmV4dDwvZW0+oaPA4MP8w/vOqjxlbT5HYW1lT3ZlclNjZW5lPC9lbT4sILzMs9DX1CA8ZW0+U0tTY2VuZTwvZW0+o6zSu7TOteO79yA8ZW0+TmV4dDwvZW0+LCA8ZW0+Q3JlYXRlPC9lbT6hozwvcD48cD608r+qIDxlbT5HYW1lT3ZlclNjZW5lLiBoPC9lbT4g1NpAZW5kICDT777kx7C808jrOjwvcD48dGFibGUgYm9yZGVyPQ=="0" cellpadding="0">
-(id)initWithSize:(CGSize)size
playerWon:(BOOL)isW 这个初始化方法多了一个参数,用于表示玩家是输还是赢。这个场景可以既用于游戏胜利也用可用于游戏失败。非常省事 :) 将 GameOverScene.m 修改为如下代码:
#import "GameOverScene.h" #import "MyScene.h"
@implementation GameOverScene
-(id)initWithSize:(CGSize)size
playerWon:(BOOL)isWon {
initWithSize:size];
if (self) {
SKSpriteNode* background = [SKSpriteNode spriteNodeWithImageNamed:@"bg.png"];
background.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
[self addChild:background];
SKLabelNode*
gameOverLabel = [SKLabelNode
labelNodeWithFontNamed:@"Arial"];
gameOverLabel.fontSize = 42;
gameOverLabel.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
if (isWon) {
gameOverLabel.text
= @"Game Won";
gameOverLabel.text
= @"Game Over";
[self addChild:gameOverLabel];
-(void)touchesBegan:(NSSet *)touches
withEvent:(UIEvent *)event {
MyScene* breakoutGameScene =
[[MyScene alloc]
initWithSize:self.size];
[self.view
presentScene:breakoutGameScene];
@end 这些代码太常见了,所以我只对有注释的部分进行介绍:创建一个 Label 用于显示胜利或失败信息。SKLabelNode 通常用于以设备字体显示文本信息。注意:要想知道设备上安装了哪些字体,可以使用这段代码:NSArray*familyNames = [UIFont familyNames]; for(NSString *familyName in familyNames ){
printf( "Family: %s \n",[familyName UTF8String] );
NSArray *fontNames =[UIFont fontNamesForFamilyName:familyName];
for( NSString *fontName infontNames ){
printf( "\tFont: %s \n", [fontName UTF8String] );
}} 当用户触摸 game over 窗口,我们再次显示游戏开始界面,以便玩家可以重新开始游戏。来试一下新场景的使用。在 MyScene.m 顶部加入导入语句:
#import "GameOverScene.h" 然后在 didBeginContact:方法中将 NSLog 语句替换为:
GameOverScene* gameOverScene = [[GameOverScene
alloc] initWithSize:self.frame.size playerWon:NO];
[self.view presentScene:gameOverScene]; 编译运行程序,进行游戏,当球拍没接住球时:不错,很有成就感吧!但搞笑的是,这个游戏怎样才能获胜? 加入砖块——并将它们击飞Adding Some Blocks – and They Are Gone…在 MyScene.m 的 initWithSize 方法中加入一些砖块:
// 1 局部变量声明
int numberOfBlocks = 3;
int blockWidth = [SKSpriteNode
spriteNodeWithImageNamed:@"block.png"].size.
float padding = 20.0f;
// 2 计算 xOffset
float xOffset = (self.frame.size.width
- (blockWidth
* numberOfBlocks +
padding * (numberOfBlocks-1))) / 2;
// 3 创建砖块并加入游戏场景
for (int i = 1; i &= numberOfB i++)
SKSpriteNode* block = [SKSpriteNode
spriteNodeWithImageNamed:@&block.png&];
block.position = CGPointMake((i-0.5f)*block.frame.size.width + (i-1)*padding + xOffset,
self.frame.size.height * 0.8f);
block.physicsBody
= [SKPhysicsBody
bodyWithRectangleOfSize:block.frame.size];
block.physicsBody.allowsRotation = NO;
block.physicsBody.friction
block.name
= blockCategoryN
block.physicsBody.categoryBitMask =
addChild:block];
} 这段代码创建了3 块砖块,以固定距离间隔并居于屏幕中轴。局部变量声明,例如砖块数量以及砖块的宽度。计算 x 偏移。即屏幕左边与第一块砖之间的间隔。用屏幕宽度减去三块砖的宽度和砖块间的空格,再除以2来得到。创建砖块,用 blockWidth、padding 和 xOffset 设置每块砖的物理属性和位置。加入砖块后的效果。编译运行程序。游戏一开始,砖块井然有序。
(window.slotbydup=window.slotbydup || []).push({
id: '2467140',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467141',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467142',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467143',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467148',
container: s,
size: '1000,90',
display: 'inlay-fix'使用SpriteKit做一个Frvr游戏 - 简书
<div class="fixed-btn note-fixed-download" data-toggle="popover" data-placement="left" data-html="true" data-trigger="hover" data-content=''>
写了24821字,被56人关注,获得了76个喜欢
使用SpriteKit做一个Frvr游戏
一,游戏怎么玩?
好吧,我又回来了,之前利用SpriteKit游戏引擎做过一个十字消除的游戏。对于不会其它引擎的人来说,SpriteKit的优点就是比较简单迅速,快速开发些有趣的小游戏。前些天,玩了一个很好玩的在线游戏Hex Frvr,是使用Html5做的,在AppStore上有可以下载玩到,在线游戏的地址点。
从这里下载完整的,在Xcode里面直接打开运行吧。玩起来的效果是这样的:
Ok,东西就是这么个东东,本文就详细描述下demo里面的内容,用SpriteKit来实现吧。
二,开始制作游戏之旅。
1,素材的准备。
好吧,之前做十字消游戏的时候,使用程序生成方块的素材。这次素材是六边形,程序这块本想研究下怎么生成的,不过学习使人进步,我最近研究了下Mac OS下的一个设计神器“Sketch”,然我由此走上了设计师的路(尼玛,老板你怎么不请美工啊)。
ok,简单使用下Sketch,画个六边形。如果不想画的哥们,请直接拿Demo工程里面的素材,不谢不谢!
Sketch绘制六边形的方法:(1)绘制六边形。a,打开Sketch,新建一个画布(A)
b,插入多边形,默认是5边形,先画一个。在右边检查器里选6,增加一个点,就生成了6变形。注意:按着Shift键拉动,调整成正六边形。
选择多边形
编辑多变形点个数
编辑多边形点数
(2)上颜色加增加立体效果。a,上颜色。在右侧检查器的Fill栏,选择喜欢颜色,然后Blending模式选择Normal。就有基本的底色了。
b,增加图像的立体感。增加增加线性渐变:Fill栏里,添加线性渐变,Blending模式选择Overlay。圆形渐变:Fill栏里,添加圆形渐变,Blending模式选择Overlay。效果特效自己可以随便设置,我都是以最上面那个点为渐变起点,最底下的点为渐变终点,强调一下立体的感觉。最终效果如图所示:
六边形素材最终效果
线性渐变和圆形渐变
c,导出素材点击export,按照提示到处png格式图片。
2,游戏玩法和规则。
游戏规则(百度词条):
“游戏的主界面是一个大六边形棋盘[2]
,六边形的每一边又由5个小六边形组成,构成共有61格的棋盘空间。玩家拖动系统自动输出的各种六边形组合置于大六边形的空白处,使之排列成完整的一行或多行并且消除得分,而连续消除会有额外Combo加分。直到棋盘上再也无法摆下任意一个六边形组合的时候,游戏就会失败。”
OK,归纳成编写程序的输入,我们要实现下面的东东:(1)61个小六边形组成的蜂巢状棋盘。如图的游戏主界面所示,主界面是如游戏名Hex所代表的蜂窝状六边形组合成的棋盘,共有61格,为了显示方便,我都边上了号码。
(2)24种有4个小六边形组成的不同形状+1个单六边形。如图2.6所示,4个小六边形所排列组合成的不同形状,就是我们要填入到棋盘格子里面的图形和形状。
待填入图形
(3)将(2)中的不同形状填入(1)中的棋盘,在横向,左斜方向和右斜方向有占满的六边形格子就消除。(4)每回合有三个(2)中的形状,填入一个补充一个。如果三个形状都无法再填入61格子的棋盘中,游戏结束。其游戏过程可以参见文章开头的动图显示。
3,游戏实现数据结构及算法说明。
(1)要有方向。
一个六边形有六个方向,因此如果要在棋盘中进行比较和消除,必须比较每一个小六边形单元六个方向的情况。很显然,我们将六边形的六个方向做一个编号:
//LeftTop:0
//RightTop:1
//Right:2
//RightBottom:3
//LeftBottom:4
typedef enum : NSInteger {
SUDNone = -1,
SUDTopLeft = 0,
SUDTopRight,
SUDBottomRight,
SUDBottomLeft,
} ShapeUnitD
其方向示意如图所示:
六边形比较方向示意图
(2)要有顺序。
如何检查2.(2)中的25种形状是否可以放入2.(1)中的棋盘呢?嗯,好了,计算机要一个一个的比较,没有你聪明。不过好在它的速度飞快!但是你要告诉电脑怎么弄。首先比较是有顺序的,对比较的不同的形状,必须规定一个比较顺序。为其中每一个单元的六边形编一个序号,表示比较的顺序。这个顺序要是一个连续的,可达的路径,计算机比较的时候能有来有回。路径可以用上面规定的方向来表示。如图选了两个形状,来说明他们的比较顺序。
“一”和“二”的两个图形里每个小六边形都有编号,其编号就是其比较顺序,1非常重要,是比较的起始点。“一”图形里,1是起始比较点,比较1后,就要比较2,2位于1的LeftBottom方位,所以要将LeftBottom记住。3位于2的RightBottom位置,4位于3的RightTop方位。所以按照1到4的顺序,比较路径就是[LeftBottom,RightBottom,RightTop],红色箭头所示。同理,“二”的图形里,比较路径就是[Right,RightBottom,LeftBottom]。注意:这个形状比较顺序非常重要,要理解清楚。
(3)要会比较。
a,第一种比较是,将图形放置到棋盘上后,看图形里的每一个六边形是否能放置到棋盘上去。在程序里面其实算法很简单,就是遍历图形里每一个小六边形,看其所在位置下的棋盘格子是否是空的,如果全都是空的就可以放上去了。b,每一次成功放置了形状后,都会补充一个新的形状。此时,要判定游戏失败条件,即游戏是否可以进行下去。将现有的没有放进棋盘的三个形状,迭代的对棋盘里的每一个位置进行一下比较,看是否能放得进去。如果三个形状都放不进棋盘,那么游戏结束了。
注意,这里的比较,就要使用到刚才定义的比较顺序了,如“一”里面的[LeftBottom,RightBottom,RightTop],因为你知道棋盘格每一个的位置,棋盘六个方向的位置也可以通过数据结构来记录,但是你需要知道放置的图形,它的比较路径和位置信息,才能够很好的便利,所以才会定义比较路径和比较起始点,有了这两个元素,比较才能进行。
PS:其实还有别的方法,仅需要图形的起始点,把起始点移到棋盘的每一格,按照比较a里的位置判断方法比较。好了,其实比较简单啦!理解下就好。
4,编程实现。
SpriteKit的用法和Cocos2dx比较像,也是将实体精灵Sprite以树形结构组织,你来规定Sprite节点的交互和动画,达到游戏的结果。在iOS9系统中加入了很多的新功能,实在值得好好研究,不过这里我们用的比较简单。大家也可以看看我之前写的《使用SpriteKit游戏引擎,做一个十字消游戏》。里面有SpriteKit的普及和基础知识,我们在这里就不对SpriteKit引擎进行过多的讲解。
游戏界面:(1)设计游戏界面布局。第一,由于是蜂巢状的六边形,每一行的个数先是增加,后来又递减,而且位置又不太相同。所以需要记录棋盘每行的单元格个数。计算出横向和纵向的距离,再进行添加。第二,需要记录蜂巢状六边形的六个方向的单元格编号,方便比较的时候搜索。我在这里使用了一个JSON文件,将每一个单元格的信息写入里面,初始化的时候读入,生成相关的信息数据结构。其结构如下,serialNum号就是单元格编号,如图2.5所示。adjacent就是邻接的单元点编号,-1代表该方向没有单元格。
"unitInfos" : [
"serialNum" : 0,
"adjacent" : "-1 -1 1 6 5 -1"
添加棋盘的代码参见Demo代码里的GameScene.m的如下代码:
- (void)addPlayground
初始化相关数据结构
SKSpriteNode *
self.unitNodeArray = [[NSMutableArray alloc] init];
self.unitTexture = [SKTexture textureWithImageNamed:@"6kuai_gray.png"];
self.unitWidth = self.unitTexture.size.
self.unitHight= self.unitTexture.size.
//2 生成每行的单元格个数,并设置起始点。
NSArray *arrayNumber = @[@5,@6,@7,@8,@9,@8,@7,@6,@5];
CGPoint startPoint = CGPointMake(CGRectGetMidX(self.frame) -2*self.unitWidth, CGRectGetHeight(self.frame)-150 );
// 3 两层循环,摆放棋盘单元格,并填入从JSON中读取的信息,放入userdata字段。
int index = 0;
int nodeCount = 0;
for (NSNumber *lineNumber in arrayNumber) {
int count = lineNumber.intV
for (int i = 0; i & i++) {
//3.1 生成单元格节点
node = [SKSpriteNode spriteNodeWithTexture:self.unitTexture];
//3.2 摆放位置
if (index &= 4) {
[node setPosition:CGPointMake(startPoint.x-XDISTANCE*index +i*self.unitWidth, startPoint.y-YDISTANCE*index)];
[node setPosition:CGPointMake(startPoint.x - XDISTANCE*((PLAYGROUNDLINE-1)-index) + i*self.unitWidth, startPoint.y - YDISTANCE*index)];
// 3.3 读取单元格信息,并填入userData
ShapeUnitInfo *unitInfo = [_unitInfoArray objectAtIndex:nodeCount];
unitInfo.unitPosition = node.
node.userData = [[NSMutableDictionary alloc] init];
[node.userData setValue:unitInfo forKey:@"unitInfo"];
[node setName:@"unitShape"];
// 3.4 加入数字标签
SKLabelNode *label = [SKLabelNode labelNodeWithText:[NSString stringWithFormat:@"%d",nodeCount]];
label.position = CGPointMake(0, 0);
label.fontColor = [UIColor blackColor];
label.fontSize = 18;
label.zPosition = 2;
[node addChild:label];
//3.5 添加节点入GameScene
[self addChild:node];
[self.unitNodeArray addObject:node];
nodeCount++;
- (void)addPlayground
初始化相关数据结构
SKSpriteNode *
self.unitNodeArray = [[NSMutableArray alloc] init];
self.unitTexture = [SKTexture textureWithImageNamed:@"6kuai_gray.png"];
self.unitWidth = self.unitTexture.size.
self.unitHight= self.unitTexture.size.
//2 生成每行的单元格个数,并设置起始点。
NSArray *arrayNumber = @[@5,@6,@7,@8,@9,@8,@7,@6,@5];
CGPoint startPoint = CGPointMake(CGRectGetMidX(self.frame) -2*self.unitWidth, CGRectGetHeight(self.frame)-150 );
// 3 两层循环,摆放棋盘单元格,并填入从JSON中读取的信息,放入userdata字段。
int index = 0;
int nodeCount = 0;
for (NSNumber *lineNumber in arrayNumber) {
int count = lineNumber.intV
for (int i = 0; i & i++) {
//3.1 生成单元格节点
node = [SKSpriteNode spriteNodeWithTexture:self.unitTexture];
//3.2 摆放位置
if (index &= 4) {
[node setPosition:CGPointMake(startPoint.x-XDISTANCE*index +i*self.unitWidth, startPoint.y-YDISTANCE*index)];
[node setPosition:CGPointMake(startPoint.x - XDISTANCE*((PLAYGROUNDLINE-1)-index) + i*self.unitWidth, startPoint.y - YDISTANCE*index)];
// 3.3 读取单元格信息,并填入userData
ShapeUnitInfo *unitInfo = [_unitInfoArray objectAtIndex:nodeCount];
unitInfo.unitPosition = node.
node.userData = [[NSMutableDictionary alloc] init];
[node.userData setValue:unitInfo forKey:@"unitInfo"];
[node setName:@"unitShape"];
// 3.4 加入数字标签
SKLabelNode *label = [SKLabelNode labelNodeWithText:[NSString stringWithFormat:@"%d",nodeCount]];
label.position = CGPointMake(0, 0);
label.fontColor = [UIColor blackColor];
label.fontSize = 18;
label.zPosition = 2;
[node addChild:label];
//3.5 添加节点入GameScene
[self addChild:node];
[self.unitNodeArray addObject:node];
nodeCount++;
(2)设置游戏相关初始化数据。这里就是读入JSON文件,并将分数置0。
- (void)unitInfoInit
// 1 初始化信息存入的数据容器,一个NSArray
if (_unitNodeArray != nil) {
_unitInfoArray = [[NSMutableArray alloc] init];
读入JSON文件
NSString *bundleDir = [[NSBundle mainBundle] bundlePath];
NSString *path = [bundleDir stringByAppendingPathComponent:@"unitInfo.json"];
NSURL *url = [NSURL fileURLWithPath:path];
NSData *data = [NSData dataWithContentsOfURL:url];
NSError *error =
// 3 解析JSON文件,并存入Arrary容器
NSDictionary *jsonDic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
NSArray *unitInfos = [jsonDic objectForKey:@"unitInfos"];
if (unitInfos != nil) {
for (NSDictionary *unitInfoDic in unitInfos) {
ShapeUnitInfo *unitInfo = [[ShapeUnitInfo alloc] init];
int x = ((NSNumber *)[unitInfoDic objectForKey:@"x"]).intV
int y = ((NSNumber *)[unitInfoDic objectForKey:@"y"]).intV
int sn = ((NSNumber *)[unitInfoDic objectForKey:@"serialNum"]).intV
NSString *adjacentString = (NSString *)[unitInfoDic objectForKey:@"adjacent"];
NSArray *adjacents = [adjacentString componentsSeparatedByString:@" "];
unitInfo.unitLocation = CGPointMake(x, y);
unitInfo.serialNumber =
[unitInfo.adjacentArray addObjectsFromArray:adjacents];
[_unitInfoArray addObject:unitInfo];
(3)三个备选容器添加.在棋盘下面的位置添加三个备选容器,如图2.5种的2所标示的位置。
- (void)addShapeFrame
//1 初始化存储容器
SKSpriteNode *
_shapePosArray = [[NSMutableArray alloc] initWithCapacity:3];
_shapeArray = [[NSMutableArray alloc] initWithCapacity:3];
// 2 生成被选位置节点,并加入到Scene中去
for (int i = 0; i & 3; i++) {
node = [[SKSpriteNode alloc] init];
node.size = CGSizeMake(100, 100);
node.position= CGPointMake(CGRectGetMidX(self.frame) + (i - 1)*120, 220);
node.name = [NSString stringWithFormat:@"shapeFrame_%d",i];
[self addChild:node];
[_shapePosArray addObject:[NSValue valueWithCGPoint:node.position]];
// 3 调用生成被选图形的接口,填充入这些位置节点。
[self shapeFill];
(4)随机生成填充形状。
上面(3)中代码的最后一步,在GameScene里面调用shapeFill方法来,填充三个备选容器。实际上是调用RandomShapeMgr.h中的RandomShapeMgr的单例对象,生成如图2.6所示的25种不同的待填入形状。重要的是将其编号,和比较队列写好,放入一个队列对象。RandomShapeMgr里的代码里面的posInfoInit方法可以研究下,节点的位置队列和比较队列如何生成好保存。
游戏交互实际上SpriteKit里面的交互和iOS应用里的交互一脉相承。由于有点击,拖动图形,放下图形等操作,所以使用如下几个方法:
a,-(void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event;b,- (void)touchesMoved:(NSSet&UITouch *& )touches withEvent:(UIEvent )event;c,- (void)touchesEnded:(NSSet&UITouch *& )touches withEvent:(UIEvent )event;
分别在点击开始,中途和结束时进行程序处理完成主要核心交互,如下三个功能:(1)点击形状,识别形状,并移动。点击形状,调用方法a。在方法a中,根据节点Name值,判断是否时需要保存处理的形状节点。如是的话,使用 _handleNode来持有。因为屏幕大小限制,平时待选的图形仅仅只有单元格大小的1/2,因此点击持有形状后,会执行动画,将待选形状扩大一倍。处理逻辑:
if ([node.name isEqual:@"shape"]) {
NSLog(@"shape");
_handleNode =
[_handleNode runAction:[SKAction scaleTo:2 duration:0.4] completion:^{
移动持有图形,调用方法b,如果_handleNode有值的话,实时更新_handleNode的位置Position值。
(2)放置形状,并判断是否能够放置入棋盘。在方法c中,判定是否能够放入棋盘。就是判断图形里的节点是否能否放在它下面的那个单元格中。处理逻辑:
if (_handleNode != nil) {
// 1 获取放入形状的所有小六边形,并获取其颜色纹理texuture
NSArray *handleShapeNodes = [_handleNode children];
SKTexture *texture = [(SKSpriteNode *)[[_handleNode children] firstObject] texture];;
// 2 遍历所有的小六边形,获取其位置,并察看该位置下是否存在Unit单元格,如果存在单元格,察看是否是被占状态。如果所有的小六边形下,都有未被占用的单元格,那么就可以放置在棋盘上了;反之,返回形状待选区域。
NSUInteger index = 0;
NSUInteger ocuppiedCount = 0;
NSMutableArray *tempArray = [[NSMutableArray alloc] init];
// 2.1 遍历小六边形
for (SKSpriteNode *child in handleShapeNodes) {
// 2.2 获取小六边形位置
CGPoint childLocation = CGPointMake(child.position.x*2 +_handleNode.position.x, child.position.y*2+_handleNode.position.y);
// 2.3 获取该位置下的所有节点
NSArray *shapeNodes = [self nodesAtPoint:childLocation];
// 2.4 看该节点下,是否存在违背占用的单元格
for (SKNode *shapeNode in shapeNodes) {
if ([self isShapeUnit:(SKSpriteNode *)shapeNode] && ![self isUnitOcuppied:(SKSpriteNode *)shapeNode]) {
ocuppiedCount++;
[tempArray addObject:shapeNode];
// 2.5 如果所有六边形都用空白的Unit可以占,那么就可以放入。
if ( index == ocuppiedCount ) {
// 2.6 执行占用,并调用shapeFill补充shape
for (SKSpriteNode *unitNode in tempArray) {
[unitNode setTexture:texture];
ShapeUnitInfo *unitInfo = [unitNode.userData objectForKey:@"unitInfo"];
unitInfo.occupy = YES;
NSLog(@"set occupy");
[_shapeArray removeObject:_handleNode];
[_handleNode removeFromParent];
[self shapeFill];
否则就将shape移动回待选区域。
NSUInteger index = [_shapeArray indexOfObject:_handleNode];
CGPoint location = [(NSValue *)[_shapePosArray objectAtIndex:index] CGPointValue];
SKAction *scale = [SKAction scaleTo:1 duration:0.3];
SKAction *move = [SKAction moveTo:location duration:0.3];
SKAction *group = [SKAction group:@[scale,move]];
group.timingMode = SKActionTimingEaseO
[_handleNode runAction:group];
_handleNode =
(3)消除判断,消除积分增加。在方法c中,还需要进行消除判断,填入形状后,是否会在横,左斜和右斜方向存在填满一行的情况,如果有就需要进行消除,并积分。
// 检查消除并积分
[self resultDealElimination];
使用数组记录下Top和Bottom行的单元格编号,并记录每一行开头的单元格编号:
// 1 每一行开头的单元格编号
NSArray *compareIndexRow = @[@0,@5,@11,@18,@26,@35,@43,@50,@56];
// 2 Top行所有元素的编号
NSArray *compareIndexTopSlash = @[@0,@1,@2,@3,@4];
// 3 Bottom行所有元素的编号
NSArray *compareIndexBottomSlash = @[@56,@57,@58,@59,@60];
涉及单元格如图:
比较单元格.png
比较方向如下图所示,1是横向,2是Top斜,3是Bottom斜:
比较方向.png
比较方向按六边形方向定义比较,具体见代码。
(4)游戏结束判断将三个待填入的图形分别比较放在棋盘里进行
// 调用检查是否能Continue
[self checkContinue];
比较方案如上述所描述,按照填入图形的比较序列,逐个对每个单元格进行比对,如果还存在可以填入的位置,游戏就可以继续,如果不存在,游戏就结束。核心比较代码:
- (BOOL)isOccupByShape:(SKSpriteNode *)shapeNode atUnit:(SKSpriteNode *)unitNode
// 1 获取该形状Shape的比较序列
NSArray *comSeqArray = (NSArray *)[shapeNode.userData objectForKey:@"shapeCompOrder"];
// 2 以读入的单元格为起始比较单元格,按照比较序列进行比较。
SKSpriteNode *tempNode = unitN
ShapeUnitInfo *nodeInfo = (ShapeUnitInfo *)[tempNode.userData objectForKey:@"unitInfo"];
if ([nodeInfo isOccupied]) {
return YES;
for (NSNumber *index in comSeqArray) {
NSInteger nodeIndex = [(NSNumber *)[nodeInfo.adjacentArray objectAtIndex:[index unsignedIntegerValue]] integerValue];
if(-1 == nodeIndex) {
return YES;
tempNode = (SKSpriteNode *)[_unitNodeArray objectAtIndex:nodeIndex];
nodeInfo = (ShapeUnitInfo *)[tempNode.userData objectForKey:@"unitInfo"];
if ([nodeInfo isOccupied]) {
return YES;
return NO;
三,游戏效果,何去何从。
好了,从这里下载完整的,在Xcode里面运行打开吧。执行效果文章开始所示。好了,其实还有很多的功能可以添加和细化。比如添加很多的动画效果,增加积分机制和加入社交化分享,广告条。很多功能可以添加,有兴趣的哥们,就在,这个工程里面,好好加油,我顶你哦!
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮
被以下专题收入,发现更多相似内容:
如果你是程序员,或者有一颗喜欢写程序的心,喜欢分享技术干货、项目经验、程序员日常囧事等等,欢迎投稿《程序员》专题。
专题主编:小...
· 272968人关注
分享 iOS 开发的知识,解决大家遇到的问题,讨论iOS开发的前沿,欢迎大家投稿~
· 29677人关注
【最新投稿方式】
为了保证专题文章的质量,暂时关闭投稿申请,改为【私信文章链接】给本专题的管理员 (花前月下) (判若...
· 8015人关注
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
选择支付方式:

我要回帖

更多关于 spritekit scenekit 的文章

 

随机推荐