您的位置:首页 > 编程语言

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

2014-01-20 18:18 453 查看

构建场景

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

· 场景(
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 移动锚点到视图的中心



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

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

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

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

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

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

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

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

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

[cpp] view
plaincopy

- (void)createSceneContent

{

self.scaleMode = SKSceneScaleModeAspectFit;

self.backgroundColor = [SKColor blackColor];

/ /在这里添加更多的场景内容

...

}

如果你希望在运行时改变场景的尺寸,那么应该用初始的场景尺寸来确定要使用的美术资产,以及任何依赖于场景尺寸的游戏逻辑。你的游戏应该重写场景的
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. 场景渲染岩石节点,它们在场景的子节点数组中的飞船节点后出现。

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

维护节点的子节点的顺序,有时会比你感兴趣的工作还要多。给每个节点一个明确的深度值并允许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
plaincopy

playerNode.name = @“player”;

monsterNode1.name = @“goblin”;

monsterNode2.name = @“ogre”;

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

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

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

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

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

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

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

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

清单4-3 寻找玩家节点

[cpp] view
plaincopy

- (SKNode *)playerNode

return [self childNodeWithName:@“player”];

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


高级搜索

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

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

表4-3 搜索语法选项
语法
描述
/
当放在搜索字符串的开头时,这表示应该对树的根节点进行搜索。
//
当放在搜索字符串的开头时,这指定搜索应从根节点开始,并在整个节点树中递归进行。这在搜索字符串之外的其他地方都是不合法的。
..
这表明搜索应该向上移到该节点的父节点中进行。
/
当放在搜索字符串的开头以外的任何地方时,这表明搜索应该移到节点的子节点中进行。
*
搜索匹配零个或多个字符。
[以逗号或破折号分隔的字符]
搜索将匹配括号内包含的任意字符。
字母和数字字符
搜索只匹配指定的字符。
表4-4展示了一些有用的搜索字符串来帮助你入门。

表4-4 搜索示例
搜寻字串
描述
/MyName
搜索根节点的子节点并匹配名为
MyName
任何节点。
//*
这个搜索字符串匹配场景中的每一个节点。
//MyName/..
搜索整个场景并匹配每个名为
MyName
节点的父节点。
A[0-9]
搜索节点的子节点并返回任何命名为
A0
A1
,...,
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
plaincopy

CGPoint positionInScene = [node.scene convertPoint:node.position fromnode:node.parent];

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

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

· 在OS X上,使用
NSEvent
对象的
locationInNode:
方法,将鼠标事件转换到节点的坐标系。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: