基础节点
为场景中显示的所有事物提供参考,观点或基础。
使用基本节点布置SpriteKit
内容
使用非可视节点定义场景的布局。
总览
SpriteKit中的每个屏幕元素都称为一个节点。 节点是视觉元素或其他节点的容器。 通过以分层形式在节点的顶部和底部添加节点,可以设置SpriteKit场景的外观。 总的来说,此结构称为节点树或节点层次结构。
这些是SpriteKit
中的基本非可视节点:
-
SKNode
是一个容器节点。 它不呈现其自身的任何内容,而是用作其子节点的布局工具。 -
SKReferenceNode
并未定义其自身的内容,而是引用了其他定义的内容或归档文件。 -
SKCameraNode
定义场景内的视点。 它的反比例适用于层次结构中除其子元素之外的所有节点。 将SKCameraNode
的子级用于应该不受缩放级别影响的UI元素。
有关视觉元素的列表,请参见Nodes that Draw,其中还包括一些使用视觉节点的示例。
SKNode
所有SpriteKit
节点的基类。
@interface SKNode : UIResponder
SKNode为其子类提供基本属性,并且可以用作其他节点的容器或布局工具。 例如,您可以将一组节点作为子节点添加到一个在场景中一起移动的SKNode上。 因为节点继承了其父节点的属性,所以更改父节点的位置也会将更改传播到其子节点。
SKNode
本身不会绘制任何内容。 它的视觉对应项在“Nodes that Draw”中列出。
从基础节点SKNode
开始
了解为所有其他节点提供的基础属性
节点按层次结构组织到节点树中,类似于视图和子视图的工作方式。 最常见的是,将节点树定义为场景节点(SKScene
)作为根节点,而其他内容节点作为后代。 场景节点运行一个动画循环,该循环处理节点上的动作,模拟物理学并渲染节点树的内容以进行显示。
节点树中的每个节点都为其子节点提供一个坐标系。 将子级添加到节点树后,可以通过设置其zPosition
属性将其放置在其父级的坐标系内。 可以通过更改节点的xScale
,yScale
和zRotation
属性来缩放和旋转其坐标系。 当缩放或旋转节点的坐标系时,此变换既应用于节点自身的内容,也应用于其后代的内容。
重要的属性
SKNode
类不执行其自身的任何绘制。但是,许多SKNode
子类呈现可视内容,因此SKNode
类理解一些可视概念:
-
frame
属性为节点的视觉内容提供了边界矩形,并通过scale
和rotation
属性对其进行了修改。如果节点的类绘制内容,则该属性为非空。每个节点子类都会以不同方式确定此内容的大小。在某些子类中,节点内容的大小是明确声明的,例如在SKSpriteNode
类中。在其他子类中,内容大小由该类使用其他对象属性隐式计算。例如,SKLabelNode
对象使用标签的消息文本和字体特征确定其内容大小。 - 通过调用
calculateAccumulatedFrame
方法检索的节点的累积帧是最大的矩形,其中包括节点的帧及其所有后代的帧。 - 其他属性,例如
alpha
和hidden
属性,会影响节点及其后代的绘制方式。
所有节点都是响应者对象,可以直接响应用户与屏幕上节点的交互。请参阅 Controlling User Interaction on Nodes。您还可以在坐标系之间转换并执行命中测试以确定点位于哪个节点,并在树中的节点之间执行相交以确定它们的物理区域是否重叠。
树中的任何节点都可以运行操作,这些操作用于为节点的属性设置动画,添加或删除节点,播放声音或执行其他自定义任务。Action
是SpriteKit
中动画系统的核心。请参阅Getting Started with Actions。
节点可以支持物理实体,该实体是模拟对象的物理属性的对象。当节点具有物理主体时,物理模拟会自动为物理主体计算一个新位置,然后移动并旋转节点以匹配该位置。请参阅Getting Started with Physics。
节点可以提供表达与场景中其他节点或位置的关系的约束。这些约束在场景渲染之前由场景自动应用。另一组约束与执行反向运动学动画的动作结合使用。请参阅Working with Inverse Kinematics。
保证在主线程中访问节点
SKViewDelegate
,SKSceneDelegate
和SKScene
的所有SpriteKit
回调都发生在主线程中,因此,这些是访问或操作节点的安全位置。 如果在后台执行其他工作,则应采用如下方式:
class ViewController: UIViewController {
let queue = DispatchQueue.global()
var makeNodeModifications = false
func backgroundComputation() {
queue.async {
// Perform background calculations but do not modify
// SpriteKit objects
// Set a flag for later modification within the
// SKScene or SKSceneDelegate callback of your choosing
self.makeNodeModifications = true
}
}
}
extension ViewController: SKSceneDelegate {
func update(_ currentTime: TimeInterval, for scene: SKScene) {
if makeNodeModifications {
makeNodeModifications = false
// Make node modifications
}
}
}
使用基础节点布局场景
即使SKNode
对象不能直接绘制内容,也可以使用一些有用的方法在应用程序中使用它们。这里有一些想法可以帮助您入门:
- 您具有从多个节点对象(精灵或其他内容节点)构建的内容。但是,您希望将此内容视为游戏中的单个对象,而无需将任何一个内容节点提升为根。一个基本节点是合适的,因为您可以在场景树中为其指定位置,然后将所有其他节点作为后代。这些单独的作品也可以相对于父母的位置进行移动或调整。
- 您希望将绘制的内容组织为一系列图层。例如,许多游戏的背景层是世界,另一层是角色,第三层是文本和其他游戏信息。其他游戏具有更多层次。将图层创建为基本节点,然后将它们按顺序插入场景中。然后,根据需要,可以使各个图层可见或不可见。
- 您需要场景中的对象不可见,但需要执行其他一些必要的功能。例如,在探索地牢的游戏中,不可见的节点可能用来表示隐藏的陷阱。当另一个节点相交时,陷阱被触发。再举一个例子,您可以将一个节点添加为另一个节点的子节点,以代表玩家视角的位置。
在树中拥有一个节点来表示这些概念有很多优点:
- 您可以通过添加或删除单个节点来添加或删除整个子树。这使场景管理变得高效。
- 您可以调整树中节点的属性,并使这些属性的影响向下传播到该节点的后代。例如,如果基本节点将sprite节点作为其子节点,则旋转基本节点也会旋转所有sprite内容。
- 您可以利用动作,物理接触和其他SpriteKit功能来实现该概念。
相关方法
- init
+ node
+ nodeWithFileNamed:
- iniWithCoder:
在场景中定位内容
通过将场景中的内容放置在其父级的坐标系中来对其进行布局。
position
节点在其父级坐标系中的位置。
@property(nonatomic) CGPoint position;
默认值是 (0.0,0.0)。
查询内容大小
在父级的坐标空间中读取节点或其子级的位置和大小。
frame
父级坐标系中的一个矩形,其中包含节点的内容,而忽略节点的子级。
@property(nonatomic, readonly) CGRect frame;
考虑到节点的xScale
,yScale
和zRotation
属性,frame
是包含节点内容的最小矩形。
由于SKNode
不会绘制自己的内容,因此其frame
大小是任意的。 确实绘制的是SKNode
的视觉子类,它们定义了frame
的大小,并带有有意义的值来包围框架的视觉内容。
要获得包围SKNode
父对象的所有子节点的rect
,请使用calculateAccumulatedFrame
。
- calculateAccumulatedFrame
在父级坐标系中返回一个矩形,该矩形包含其自身和所有子节点的位置和大小。
- (CGRect)calculateAccumulatedFrame;
该frame
考虑了子树中每个节点的xScale
,yScale
和`zRotation属性的累积影响。
配置绘图顺序
了解SpriteKit
如何从上到下将场景的节点分层。
总览
场景渲染的标准行为遵循一对简单的规则(如图1所示):
- 父级在渲染子级之前先绘制其内容。
-
子级以它们在子级数组中出现的顺序呈现。
在上图中,直升机主体及其组件都是天空节点的子代。 因此,场景呈现其内容如下:
- 场景进行渲染,将其内容清除为背景色。
- 场景将渲染天空节点。
- 天空节点按照它们作为子级添加的顺序渲染其子级(直升机主体及其组件)。
相对于父节点的绘图顺序
维护节点的孩子的秩序可能是很多工作。 相反,您可以为每个节点指定场景中的明确高度。 您可以通过设置节点的zPosition
属性来执行此操作。 zPosition
是节点相对于其父节点的高度,就像节点的position
属性代表其相对于父节点位置的x
和y
位置一样。 因此,您可以使用zPosition
将节点放置在父级位置的上方或下方。
考虑zPosition
时,以下是呈现节点树的方式:
- 每个节点的全局
zPosition
是通过将其zPosition
递归添加到其父级的zPosition
来计算的。 - 从最小的
zPosition
到最大的zPosition
依次绘制节点。
3.如果两个节点共享相同的zPosition
,则先呈现祖先,然后按照子顺序呈现同级。
同级顺序性能(Sibling Order Performance)
SpriteKit
使用基于高度节点及其在节点树中的位置的确定性渲染顺序。但是,由于渲染顺序是确定性的,因此SpriteKit
可能无法应用某些渲染优化,而其他方面可能会应用。例如,如果SpriteKit
可以收集共享相同纹理和绘制模式的所有节点并通过一次绘制过程绘制它们,则可能会更好。要启用这些优化,请将视图的ignoresSiblingOrder
属性设置为YES。
当您忽略同级顺序时,SpriteKit
使用图形硬件渲染节点,以使它们看起来按zPosition
排序。它将节点按绘制顺序排序,以减少渲染场景所需的绘制调用次数。但是使用这种优化的绘制顺序,您无法预测共享相同高度的节点的渲染顺序。每次渲染新帧时,渲染顺序可能会更改。在许多情况下,这些节点的绘制顺序并不重要。例如,如果节点的高度相同但在屏幕上不重叠,则可以按任何顺序绘制它们。
图2显示了一个使用zPosition
确定渲染顺序的树的示例。在此示例中,直升机的机体高度为100,其子代相对于其高度进行渲染。两个转子节点共享相同的高度,但不重叠。
图2仅深度渲染可以提高性能
来自不同父母的交错子节点
由于子节点的zPosition
已添加到其父节点的zPosition
中,因此可以使子节点与其他父节点交错。 图3显示了创建两个方形父节点的代码,每个父节点都有一个圆形子节点。 第一个节点的zPosition
为10,其子节点的zPosition
为10。第二个节点的zPosition
为15,其子节点的zPosition为
10。
一旦添加到场景中,第一个节点的子节点的有效全局zPosition
为20,第二个节点的子节点的有效全局zPosition
为25,从而产生交织效果。
示例:交织复合节点
func addNodesTo(scene: SKScene,
color: SKColor, position: CGPoint,
parentZ: CGFloat, childZ: CGFloat) {
let diameter: CGFloat = 250
let rect = CGRect(origin: position,
size: CGSize(width: diameter, height: diameter))
let parentNode = SKShapeNode(rect: rect)
let childNode = SKShapeNode(ellipseIn: rect.insetBy(dx: 10, dy: 10))
[(parentNode, scene, parentZ), (childNode, parentNode, childZ)].forEach {
node, parent, zPosition in
node.fillColor = color
node.strokeColor = .white
node.zPosition = zPosition
parent.addChild(node)
}
}
addNodesTo(scene: scene,
color: .red,
position: CGPoint(x: 300, y: 300),
parentZ: 10,
childZ: 10)
addNodesTo(scene: scene,
color: .blue,
position: CGPoint(x: 350, y: 250),
parentZ: 15,
childZ: 10)
由于zPosition
是跨父子节点关系添加的,因此您可以同时使用树顺序和zPositions
来确定场景的渲染顺序。 渲染复杂场景时,应禁用排序行为,并使用节点的zPositions创建确定性的场景顺序。
重要信息
SKCropNode
和SKEffectNode
节点类可更改场景渲染行为。 这些节点的子级被独立渲染为单独的节点树,结果被渲染到包含作物或效果节点的树中。 有关更多信息,请参见使用其他节点类型。
zPosition
默认值为0.0。 z
轴正向观察者投影,因此具有较大z
位置值的节点更靠近观察者。 渲染节点树时,将计算每个节点的高度(以绝对坐标表示),然后将树中的所有节点从最小的z
位置值渲染到最大的z
位置值。 如果多个节点共享相同的z位置,则会对这些节点进行排序,以便在其子级之前绘制父级节点,并按照它们在其父级的子级数组中出现的顺序渲染同级。 命中测试以相反的顺序进行处理。
SKView
类的ignoresSiblingOrder
属性控制是否为相同z
位置的节点启用节点排序。
重要信息
效果节点或裁剪节点(Descendants of an effect node or a crop node )的不会与同一树中的其他节点一起渲染。 效果节点(SKEffectNode
)将其子级作为单独的节点树渲染到专用帧缓冲区中,然后将合成图像渲染到包含效果节点的树中。 裁剪节点(SKCropNode
)的工作原理类似; 它分别渲染遮罩,然后使用遮罩将其实际后代渲染到包含作物节点的树中。
使用节点的深度添加效果
SpriteKit
仅使用zPosition
值来确定命中测试和绘制顺序。 您也可以将z
位置实现自己的游戏效果。 例如,您可以使用节点的高度来确定其渲染方式或如何在屏幕上移动。 这样,您可以模拟雾或视差效果。 SpriteKit
不会为您创建这些效果。 通常,您可以通过在渲染场景之前立即处理场景来实现它们。
缩放和旋转
zRotation
绕z轴的欧拉旋转(以弧度为单位)。
@property(nonatomic) CGFloat zRotation;
默认值为0.0,表示不旋转。 正值表示逆时针旋转。 旋转坐标系时,它会影响节点及其后代。 旋转会影响节点的框架属性,命中测试,渲染和其他类似特征。
- setScale:
- (void)setScale:(CGFloat)scale;
设置节点的xScale
和yScale
属性
xScale
比例因子,乘以节点及其子节点的宽度。
@property(nonatomic) CGFloat xScale;
xScale
属性缩放节点及其所有子类的宽度。 比例值会影响节点框架的计算方式,命中测试区域,绘制方式以及其他类似特征。 默认值为1.0。
yScale
比例因子,乘以节点及其子节点的宽度。
@property(nonatomic) CGFloat yScale;
yScale
属性缩放节点及其所有后代的高度。 比例值会影响节点框架的计算方式,命中测试区域,绘制方式以及其他类似特征。 默认值为1.0。
访问相关节点
了解节点如何遵循其坐标系。
总览
将节点放置在节点树中时,其位置属性会将其放置在其父级提供的坐标系内。 SpriteKit
在iOS
和macOS
上使用相同的坐标系。
点作为位置单位
将节点放置在节点树中时,其位置属性会将其放置在其父级提供的坐标系内。 SpriteKit
在iOS
和macOS
上使用相同的坐标系。 下图显示了SpriteKit
坐标系。 与UIKit
或AppKit
一样,坐标值以点为单位。 必要时,渲染场景时将点转换为像素。 x坐标正向右移,y坐标正向屏幕上移。
极坐标旋转(Polar Coordinate Rotation)
SpriteKit
还具有标准的旋转约定。 显示极坐标惯例。 0弧度的角度指定正x轴。 正角为逆时针方向。
通过将节点的zRotation
属性设置为所需的弧度角来旋转节点。 如果您更喜欢以度为单位工作,则以下代码显示了如何编写对CGFloat
的扩展,以在两者之间进行转换。 示例将spriteNode
逆时针旋转30度。
extension CGFloat {
func degreesToRadians() -> CGFloat {
return self * CGFloat.pi / 180
}
}
let rabbitTexture = SKTexture(imageNamed: "rabbit.png")
let spriteNode = SKSpriteNode(texture: rabbitTexture)
spriteNode.zRotation = CGFloat(30).degreesToRadians()
相关属性
修改和访问节点树
您可以通过在节点之间创建父子关系来创建节点树。 每个节点维护一个有序的子级列表,通过读取节点的children
属性来引用该列表。 树中子级的顺序会影响场景处理的许多方面,包括命中测试和渲染,因此适当地组织节点树很重要。
- addChild:
- insertChild:atIndex:`
- isEqualToNode:
如果该节点是父节点的后代,则为YES;否则为。 否则没有。
- moveToParent:
- removeFromParent
- removeAllChildren
- removeChildrenInArray:
- inParentHierarchy:
自定义节点
Customizing the Behavior of a Node
向子类传播属性(修改后会影响子节点的属性)
按名称访问节点
通常组织场景树中的节点来确定场景的精确渲染顺序,而不是确定这些节点在场景中扮演的角色。 您可能还从存档文件加载节点,而不是在运行时直接实例化它们,因此您可能需要能够在节点树中找到特定的节点。 为此,您需要为节点提供名称,然后搜索这些名称。
节点名称通常在您的应用程序中有两个作用:
- 您可以编写自己的代码,以根据节点名称实现游戏逻辑。 例如,当两个物理对象碰撞时,可以使用节点名称来确定碰撞如何影响游戏玩法。
- 您可以搜索具有特定名称的节点。 通常,第一次加载场景时,只需执行一次。
为节点起名
节点的名称属性应为字母数字字符串,且不带标点符号。 以下代码显示了如何命名三个不同的节点以将它们彼此区分开。
playerNode.name = @"player";
monsterNode1.name = @"goblin";
monsterNode2.name = @"ogre";
在树中命名节点时,请确定这些名称是否唯一。 如果一个节点的名称是唯一的,则在场景树中不得包含多个使用该名称的节点。 另一方面,如果节点名称在您的应用中不是唯一的,则它可能表示相关节点的集合。 例如,在上面的代码中,游戏中可能有多个妖精,您可能希望使用相同的名称来识别它们。 但是玩家可能是游戏中的唯一节点。
用名称搜索节点
SKNode
类实现以下用于搜索节点树的方法:
-
childNodeWithName:
方法搜索节点的子节点,直到找到匹配的节点,然后停止并返回该节点。此方法通常用于搜索具有唯一名称的节点。 -
enumerateChildNodesWithName:usingBlock:
方法搜索节点的子节点,并为找到的每个匹配节点调用一次块。当您要查找共享相同名称的所有节点时,可以使用此方法。 -
objectForKeyedSubscript:
方法返回与特定名称匹配的节点数组。
以下代码显示了如何在场景类上创建方法以找到播放器节点。您可以在代码内部使用类似这样的方法来加载和准备场景。
-(SKNode *)playerNode
{
return [self childNodeWithName:@“ player”];
}
在场景上调用此方法时,场景将在其子级(仅对其子级)中搜索其name
属性与搜索字符串匹配的节点,然后返回该节点。指定搜索字符串时,可以指定节点名称或类名称。例如,如果为播放器节点创建自己的子类并将其命名为PlayerSprite
,则可以将PlayerSprite
指定为搜索字符串,而不是player
。相同的节点将被返回。
使用高级搜索
表一:搜索语法选项
语法 | 作用 |
---|---|
/ | 如果将其放在搜索字符串的开头,则表示应该在树的根节点上执行搜索。 如果将其放置在搜索字符串开头以外的任何位置,则表明搜索应移至该节点的子级。 |
// | 当放置在搜索字符串的开头时,这指定搜索应从根节点开始,并在整个节点树中递归执行。 否则,它将从其当前位置执行递归搜索。 |
. | 引用当前节点。 |
.. | 搜索应上移到该节点的父节点。 |
* | 搜索匹配零个或多个字符。 |
[characters delimited by commas or dashes] | 搜索与括号内包含的任何字符匹配。 |
alphanumeric characters | 搜索仅匹配指定的字符。 |
官方示例:
更改节点可见性
Running Actions
在Sprite上创建、配置和运行Actions
总览
当您要为场景的内容设置动画时,您告诉节点运行SKAction的实例。 当场景处理动画帧时,将执行actions
。 一些actions
在动画的单个帧中完成,而其他actions
则在完成之前将更改应用于动画的多个帧。 操作最常见的用途是为节点的属性设置动画。 例如,您可以创建移动节点,缩放或旋转节点或淡化其透明度的actions
。 但是,actions
也可以更改节点树,播放声音甚至执行自定义代码。
未完。。待续