Sprite Kit编程指南(4)-构建场景

构建场景


对于场景的使用,你已经学过了很多的东西。这里对重要的事实再快速回顾一下:

·      场景(SKScene对象),用来提供SKView对象要渲染的内容。

·      场景的内容被创建成树状的节点对象。场景是根节点。

·      在场景由视图呈现时,它运行动作并模拟物理,然后渲染节点树。

·      你可以通过子类化SKScene类创建自定义的场景。

心中有了这些基本概念之后,是时候来学习更多关于节点树和建设场景的知识了。


节点给子节点提供坐标系


当一个节点被放置在节点树中时,它的position属性把它定位在由它的父节点提供的坐标系内。Sprite Kit在iOS和OS X中使用相同的坐标系。图4-2展示了Sprite Kit的坐标系。与UIKit或AppKit一样,坐标值用点来测量;如果必要,在渲染场景时会把点转换为像素。正数的x坐标在右边而正数的y坐标在屏幕上方。

图4-1   SpriteKit坐标系

Sprite Kit还有一个标准的旋转约定(rotation convention)。图4-2展示了相反的坐标约定。弧度为0的角指定正x轴。沿反时针方向是正角度。

图4-2   旋转坐标约定

当你的仅使用Sprite Kit代码时,一致的坐标系意味着你可以轻松地在游戏的iOS和OS X版本之间共享代码。然而,它更意味着当你编写特定OS专用(OS-specific)的用户界面代码时,你可能需要在操作系统的视图坐标约定与Sprite Kit坐标系之间进行转换。最常见的情况就是使用iOS视图,它们有一个不同的坐标约定。


只有某些节点包含内容


不是所有的节点都绘制内容。例如,SKSpriteNode类绘制一个精灵,但SKNode类不画任何东西。读取某个指定节点对象的frame属性,你就可以知道它是否绘制内容。节点在父节点的坐标系中绘制,frame代表了它在该坐标系中的可视区域。如果节点绘制内容,frame具有一个非零的尺寸。对于场景,frame总是反映场景坐标空间中的可见部分。

如果一个节点有绘制内容的后代节点,节点的子树也有可能提供内容,即使它本身并不提供任何内容。你可以调用节点的calculateAccumulatedFrame方法来检索一个矩形,它包括整个绘制节点及它所有后代的区域。


创建场景


场景由视图来呈现。它的很多属性对视图如何呈现场景都有影响。这些属性允许你定义场景的原点位置和场景的尺寸。如果场景的尺寸与视图不匹配,你还可以定义场景缩放方式以适合视图。


场景的尺寸定义其可见区域

在场景首次初始化时,它的size属性由指定初始化器配置。场景的尺寸以点为单位指定场景中可见部分的尺寸。这只用于指定场景的可见部分。树中的节点可以定位在该区域之外,这些节点仍由场景处理,但被渲染器(renderer)忽略。


使用锚点在视图中定位场景的坐标系

缺省情况下,一个场景的原点被放置在视图的左下角上,如图4-3中所示。因此,一个场景初始化为宽1024和高768,在左下角是原点(0,0),右上角坐标是(1024,768)。frame包含(0,0)-(1024,768)

场景的position属性被Scene Kit忽略,因为场景始终是一个节点树的根节点。它的默认值是CGPointZero,且你不能改变它。但是,你可以通过设置场景的anchorPoint属性移动它的原点。锚点在单位坐标空间中指定,并选择封闭视图中的一个点。

图4-3   默认锚一个场景是在左下角的视图

锚点的默认值是CGPointZero,放置于左下角。场景的可见坐标空间是从(0,0)(width,hight) 。对于不滚动场景内容的游戏,默认的锚点是最有用的。

锚点第二模式(second-mode)的值通常是(0.5,0.5),在中间的视图,如图4-4中所示,把场景的原点定在视图的中心。场景的可视坐标空间是从(-width/2,-hight/ 2)(width/2,hight/2)当你想轻松地相对屏幕的中心定位节点时,把场景的锚点定在中心是最有用的,比如一个滚动游戏。

图4-4   移动锚点到视图的中心

总结一下,anchorPointsize属性用来计算场景的frame,frame包含了场景的可见部分。


缩放场景的内容以适合视图

场景渲染后,它的内容被复制到呈现视图。如果视图和场景的尺寸相同,则内容可以直接复制到视图中。如果两者不一样,那么场景会被缩放以适合视图。scaleMode属性决定内容如何缩放。

当你设计游戏时,你应该决定处理场景的sizescaleMode属性的战略。以下是一些最常见的策略:

·      以恒定尺寸实例化场景,并且永远不改变它。必要时允许Sprite Kit把内容缩放到视图。这场样景有一个可预见的坐标系统和frame。然后,你的美术资产和游戏逻辑可以基于这个坐标系。

·      调整游戏中的场景的尺寸。在必要的地方,调整你的游戏逻辑和美术资产来匹配场景的尺寸。

·      scaleMode属性设置为SKSceneScaleModeResizeFill。Sprite Kit会自动调整场景的尺寸,使其始终与视图的尺寸相匹配。在必要的地方,调整你的游戏逻辑和美术资产来匹配场景的尺寸。

当你计划使用一个恒定尺寸的场景,清单4-1展示了一个典型的实现。与你在深入Sprite Kit创建的例子一样,这个代码指定了第一次呈现场景时要执行的方法。这个方法配置场景的属性,包括它的缩放模式,然后添加内容。在此示例中,缩放模式被设置为SKSceneScaleModeAspectFit,它在两个维度上以相同的比例缩放内容,并确保所有的场景的内容都可见。在必要的地方,这种模式会添加黑边(letterboxing)。

清单4-1   对一个固定尺寸的场景使用缩放模式

[cpp]  view plain copy
  1.  - (void)createSceneContent  
  2. {  
  3.     self.scaleMode = SKSceneScaleModeAspectFit;  
  4.     self.backgroundColor = [SKColor blackColor];  
  5. / /在这里添加更多的场景内容  
  6. ...  
  7. }  

如果你希望在运行时改变场景的尺寸,那么应该用初始的场景尺寸来确定要使用的美术资产,以及任何依赖于场景尺寸的游戏逻辑。你的游戏应该重写场景的didChangeSize:方法,每当场景变化尺寸时会调用此方法。当这个方法被调用时,你应该更新场景的内容,以匹配新的尺寸。


创建节点树


你可以通过创建节点之间的父子关系的方式来创建节点树。每个节点维护一个有序的子节点列表,可以通过读取节点的children属性进行引用。子节点在树中的顺序会影响场景处理的多个方面,包括碰撞测试(hit testing)和渲染。所以,适当地组织节点树是很重要的。

表4-1列出了构建节点树最常用的方法。完整的方法列表在SKNode类参考中提供。

表4-1   操作节点树的常用方法

方法

描述

addChild:

添加一个节点到接收者的子节点列表的末尾。

insertChild:atIndex:

插入一个孩子到接收者的子节点列表中的特定位置。

removeFromParent

从父节点中移除接收节点。

当你需要直接调整节点树,可以使用表4-2中的属性查看(uncover)树的结构。

表4-2   横移节点树

属性

描述

children

接收节点的子节点所形成的SKNode对象数组。

parent

如果该节点是另一个节点的子节点,这个属性指向父节点。否则,它为nil。

scene

如果该节点包含在场景中的任何地方,它返回作为节点树的根的场景节点。否则,它为nil。


理解节点树的绘制顺序

场景渲染的标准行为遵循以下一对简单的规则:

·      父节点先绘制自身的内容再渲染子节点。

·      子节点以它们在子节点数组中的顺序依次渲染。

图4-5展示了如何渲染有三个子节点的节点。

图4-5   绘制家长的前儿童

在你在“深入Sprite Kit写的代码中,创建了一个场景,还有一个飞船和多们岩石。两个灯被指定为飞船的子节点,而飞船和岩石又是场景的子节点。因此,场景用以下方式渲染其内容:

1.    场景渲染它本身,清除内容为它的背景色。

2.    场景渲染飞船节点。

3.    飞船节点渲染它的子节点,即飞船上的灯光。

4.    场景渲染岩石节点,它们在场景的子节点数组中的飞船节点后出现。

重要提醒:SKCropNodeSKEffectNode节点类轻微地改变了场景的渲染行为。它们不绘制自己的内容,而是改变它们的子节点在场景中的渲染方式。虽然如此,还是用相同的绘制顺序。要想了解更多信息,请参阅使用其他节点类型

维护节点的子节点的顺序,有时会比你感兴趣的工作还要多。给每个节点一个明确的深度值并允许Sprite Kit管理你的绘制顺序,会更容易。你可以使用节点的zPosition属性这样做。当一个节点创建时,zPosition属性设为0.0。通过设置节点的z轴位置,相对于它的同级节点,你让它更靠近或更远离顶层的渲染顺序。下面是z轴位置添加后场景的渲染方式:

·      父节点先绘制自身的内容再渲染子节点(不变)。

·      父节点渲染子节点从z值最大的孩子开始,并从z值最小的孩子结束。所以,z轴位置表示从子节点到一个假想的摄像机(camera)位置的距离。

·      如果两个子节点有相同的z值,则在数组中较早出现的那个先绘制。

图4-5展示了有三个子节点的节点。通常情况下,子节点将按它们出现在子节点数组的顺序依次渲染。然而,在这种情况下,这三个节点有自定义的深度值,导致它们以不同的顺序渲染。

图4-6   子节点按深度顺序渲染。


碰撞测试的顺序与绘制顺序相反

当Sprite Kit处理场景内的触摸或鼠标事件时,它在场景中查找想接受该事件的最接近节点。如果该节点不想处理事件,则检查下一个最接近的节点,依此类推。处理碰撞测试的顺序基本上是绘制顺序的反方向:

1.    父节点只在它的子节点传给它后才接受事件。

2.    子节点从最小的z值到最大的z值进行处理。

3.    如果两个子节点有相同的z值,先测试数组中后出现的那个。

在碰撞测试中要考虑一个节点,它的userInteractionEnabled属性必须设置为YES。场景节点以外的任何节点的默认值都是NO。要接收事件的节点需要从它的父类(iOS上的UIResponder 和OS X上的NSResponder)实现适当的响应方法。这是你在Sprite Kit中必须实现特定平台的代码为数不多的地方之一。

有时候,你也想直接查找节点,而不是依赖于标准的事件处理机制。Sprite Kit允许你问一个节点是否有任何的后代节点与坐标系的特定点相交。调用nodeAtPoint:方法找到的第一个与该点相交的后代节点,或使用nodesAtPoint:方法接收与该点相交所有节点的数组。


使用节点的深度来添加其他效果

Sprite Kit只使用zPosition值来确定碰撞测试和绘制顺序。但是,你可以使用你指定的值来实现自己的游戏特效。例如,你可以:

·      使用的节点的深度来确定节点在屏幕上移动的速度。通过增加不同深度的节点,你可以模拟视差滚动(parallax scrolling)。

·      使用节点的深度来影响它渲染的方式。


搜索节点树

通过组织树中的节点来确定精确的场景渲染顺序,而不是通过那些节点在你游戏中扮演的角色。正因为如此,SKNode类提供了name属性。你可以命名一个节点,以区别于树中的其他节点,然后搜索这些节点。

节点的名称应该是没有任何标点的字母数字字符串。清单4-2展示了你可以如何命名三个不同的节点来区分它们彼此。

清单4-2   命名一组节点

[cpp]  view plain copy
  1. playerNode.name = @“player”;  
  2. monsterNode1.name = @“goblin”;  
  3. monsterNode2.name = @“ogre”;  

当你的命名游戏的节点时,你应该决定名称是否是唯一的。如果你决定一个节点名是唯一的,那么该名字就是为了识别该节点而不是其他。另一方面,如果节点的名字不是你的游戏中唯一的,它通常代表了相关节点的集合。例如,在清单4-2中,可能游戏中有多个小妖精,你或许想用相同的名称识别它们。但玩家可能是游戏中唯一的节点。

在你的应用程序中,节点名称通常有两个目的:

·      你可以根据节点的名称编写自己的实现游戏逻辑的代码。例如,两个物理对象碰撞时,你可能会使用节点名称来确定碰撞如何影响游戏。

·      SpriteKit还为你提供了一些强大的工具来搜索场景内的节点。

SKNode类实现了搜索节点树的两种方法:

·      childNodeWithName:方法搜索节点的子节点,直到找到一个匹配的节点,然后停止并返回该节点。这种方法通常用于对具有唯一名称的节点进行搜索。

·      enumerateChildNodesWithName:usingBlock:方法搜索节点的子节点,并在找到的每个匹配的节点调用一次block。当你想找到的所有节点共享同一个名称时,你可以使用此方法。

清单4-3展示了在你的场景类上你可以如何创建方法来查找玩家​​节点。

清单4-3   寻找玩家节点

[cpp]  view plain copy
  1. - (SKNode *)playerNode  
  2.   
  3.    return [self childNodeWithName:@“player”];  

当这个方法在场景上调用时,场景搜索它的子节点(且仅搜索子节点)中名称属性匹配搜索字符串的节点,然后返回这个节点。当指定搜索字符串时,你可以指定节点的名称或类的名称。例如,如果你为玩家节点创建了自己的子类,并把它命名为PlayerSprite,那么你可以指定PlayerSprite作为搜索字符串代替player;它将返回相同的节点。


高级搜索

默认的搜索只搜索一个节点的子节点,而且必须完全匹配节点或类的名称。然而,Sprite Kit提供了一个表达式搜索语法,允许你进行更高级的搜索。例如,你可以像之前一样做同样的搜索,但搜索整个场景树。或者你可以搜索节点的子节点,但匹配某个模式,而不需要精确匹配。

表4-3描述了不同的语法选项。搜索使用常见的正则表达式语义。

表4-3   搜索语法选项

语法

描述

/

当放在搜索字符串的开头时,这表示应该对树的根节点进行搜索。

//

当放在搜索字符串的开头时,这指定搜索应从根节点开始,并在整个节点树中递归进行。这在搜索字符串之外的其他地方都是不合法的。

..

这表明搜索应该向上移到该节点的父节点中进行。

/

当放在搜索字符串的开头以外的任何地方时,这表明搜索应该移到节点的子节点中进行。

*

搜索匹配零个或多个字符。

[以逗号或破折号分隔的字符]

搜索将匹配括号内包含的任意字符。

字母和数字字符

搜索只匹配指定的字符。

表4-4展示了一些有用的搜索字符串来帮助你入门。

表4-4   搜索示例

搜寻字串

描述

/MyName

搜索根节点的子节点并匹配名为MyName任何节点。

//*

这个搜索字符串匹配场景中的每一个节点。

//MyName/..

搜索整个场景并匹配每个名为MyName节点的父节点。

A[0-9]

搜索节点的子节点并返回任何命名为A0A1,...,A9的子节点。

Abby/Normal

搜索节点的孙子节点并返回任何名称是Normal且其父节点名为Abby的节点。

//Abby/Normal

搜索整个场景并返回任何名称是Normal且其父节点名为Abby的节点。


节点的很多属性适用于它的后代


当你改变一个节点的属性,效果往往传播到该节点的后代。净效果是一个子节点的渲染不仅基于它自身的属性,也基于它祖先的属性。

表4-5   属性影响节点的后代

属性

描述

xScale,yScale

节点的坐标系通过这两个因素缩放。该属性影响坐标转换、节点的frame、绘制和碰撞测试。它的后代也同样地缩放。

zRotation

节点的坐标系通过这个因素旋转。该属性影响坐标转换、节点的frame、绘制和碰撞测试。它的后代也同样地缩放。

alpha

如果该节点是使用混合模式渲染的,混合操作发生之前alpha值会乘以任意alpha值。它的后代也同样受到影响。

hidden

如果一个节点是隐藏的,它和它的所有后代都不渲染。

speed

一个节点处理动作的速度与该值相乘。它的后代也同样受到影响。


坐标空间之间的转换


在使用节点树时,有时你需要把位置从一个坐标空间转换到另一个。例如,当你指定物理系统中的关节(joints),关节位置被指定在场景坐标。所以,如果你在本地坐标系有那些点,你需要将它们转换为场景的坐标空间。

清单4-4展示了如何将一个节点的位置转换到场景坐标系中。场景被要求进行转换。记住一个节点的位置在它父节点的坐标系统中指定,所以代码传递node.parent作为要转换的节点。你可以通过调用convertPoint:toNode:法执行反向的相同转换。

清单4-4   转换节点到场景坐标系统

[cpp]  view plain copy
  1. CGPoint positionInScene = [node.scene convertPoint:node.position fromnode:node.parent];  

你需要进行坐标转换的一个情况是在执行事件处理的时候。鼠标和触摸事件需要从window坐标转换到视图坐标,并从那里进入场景。为了简化你需要写的代码, Sprite Kit增加了一些方便的方法:

·      在iOS上,使用UITouch对象的locationInNode:previousLocationInNode:触摸位置转换到节点的坐标系。

·      在OS X上,使用NSEvent对象的locationInNode:方法,将鼠标事件转换到节点的坐标系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值