iOS SpriteKit - SKNode

关联章节
iOS SpriteKit 文档
iOS SpriteKit - Scene

基础节点

为场景中显示的所有事物提供参考,观点或基础。

使用基本节点布置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属性将其放置在其父级的坐标系内。 可以通过更改节点的xScaleyScalezRotation属性来缩放和旋转其坐标系。 当缩放或旋转节点的坐标系时,此变换既应用于节点自身的内容,也应用于其后代的内容。

重要的属性

SKNode类不执行其自身的任何绘制。但是,许多SKNode子类呈现可视内容,因此SKNode类理解一些可视概念:

  • frame属性为节点的视觉内容提供了边界矩形,并通过scalerotation属性对其进行了修改。如果节点的类绘制内容,则该属性为非空。每个节点子类都会以不同方式确定此内容的大小。在某些子类中,节点内容的大小是明确声明的,例如在SKSpriteNode类中。在其他子类中,内容大小由该类使用其他对象属性隐式计算。例如,SKLabelNode对象使用标签的消息文本和字体特征确定其内容大小。
  • 通过调用calculateAccumulatedFrame方法检索的节点的累积帧是最大的矩形,其中包括节点的帧及其所有后代的帧。
  • 其他属性,例如alphahidden属性,会影响节点及其后代的绘制方式。

所有节点都是响应者对象,可以直接响应用户与屏幕上节点的交互。请参阅 Controlling User Interaction on Nodes。您还可以在坐标系之间转换并执行命中测试以确定点位于哪个节点,并在树中的节点之间执行相交以确定它们的物理区域是否重叠。

树中的任何节点都可以运行操作,这些操作用于为节点的属性设置动画,添加或删除节点,播放声音或执行其他自定义任务。ActionSpriteKit中动画系统的核心。请参阅Getting Started with Actions

节点可以支持物理实体,该实体是模拟对象的物理属性的对象。当节点具有物理主体时,物理模拟会自动为物理主体计算一个新位置,然后移动并旋转节点以匹配该位置。请参阅Getting Started with Physics

节点可以提供表达与场景中其他节点或位置的关系的约束。这些约束在场景渲染之前由场景自动应用。另一组约束与执行反向运动学动画的动作结合使用。请参阅Working with Inverse Kinematics

保证在主线程中访问节点

SKViewDelegateSKSceneDelegateSKScene的所有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;

考虑到节点的xScaleyScalezRotation属性,frame是包含节点内容的最小矩形。

由于SKNode不会绘制自己的内容,因此其frame大小是任意的。 确实绘制的是SKNode的视觉子类,它们定义了frame的大小,并带有有意义的值来包围框架的视觉内容。

要获得包围SKNode父对象的所有子节点的rect,请使用calculateAccumulatedFrame

- calculateAccumulatedFrame

在父级坐标系中返回一个矩形,该矩形包含其自身和所有子节点的位置和大小。

- (CGRect)calculateAccumulatedFrame;

frame考虑了子树中每个节点的xScaleyScale和`zRotation属性的累积影响。


配置绘图顺序

了解SpriteKit如何从上到下将场景的节点分层。

总览

场景渲染的标准行为遵循一对简单的规则(如图1所示):

  • 父级在渲染子级之前先绘制其内容。
  • 子级以它们在子级数组中出现的顺序呈现。


    图1

在上图中,直升机主体及其组件都是天空节点的子代。 因此,场景呈现其内容如下:

  1. 场景进行渲染,将其内容清除为背景色。
  2. 场景将渲染天空节点。
  3. 天空节点按照它们作为子级添加的顺序渲染其子级(直升机主体及其组件)。
相对于父节点的绘图顺序

维护节点的孩子的秩序可能是很多工作。 相反,您可以为每个节点指定场景中的明确高度。 您可以通过设置节点的zPosition属性来执行此操作。 zPosition是节点相对于其父节点的高度,就像节点的position属性代表其相对于父节点位置的xy位置一样。 因此,您可以使用zPosition将节点放置在父级位置的上方或下方。

考虑zPosition时,以下是呈现节点树的方式:

  1. 每个节点的全局zPosition是通过将其zPosition递归添加到其父级的zPosition来计算的。
  2. 从最小的zPosition到最大的zPosition依次绘制节点。
    3.如果两个节点共享相同的zPosition,则先呈现祖先,然后按照子顺序呈现同级。

同级顺序性能(Sibling Order Performance)

SpriteKit使用基于高度节点及其在节点树中的位置的确定性渲染顺序。但是,由于渲染顺序是确定性的,因此SpriteKit可能无法应用某些渲染优化,而其他方面可能会应用。例如,如果SpriteKit可以收集共享相同纹理和绘制模式的所有节点并通过一次绘制过程绘制它们,则可能会更好。要启用这些优化,请将视图的ignoresSiblingOrder属性设置为YES。

当您忽略同级顺序时,SpriteKit使用图形硬件渲染节点,以使它们看起来按zPosition排序。它将节点按绘制顺序排序,以减少渲染场景所需的绘制调用次数。但是使用这种优化的绘制顺序,您无法预测共享相同高度的节点的渲染顺序。每次渲染新帧时,渲染顺序可能会更改。在许多情况下,这些节点的绘制顺序并不重要。例如,如果节点的高度相同但在屏幕上不重叠,则可以按任何顺序绘制它们。

图2显示了一个使用zPosition确定渲染顺序的树的示例。在此示例中,直升机的机体高度为100,其子代相对于其高度进行渲染。两个转子节点共享相同的高度,但不重叠。

图2仅深度渲染可以提高性能


图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)
图3结果

由于zPosition是跨父子节点关系添加的,因此您可以同时使用树顺序和zPositions来确定场景的渲染顺序。 渲染复杂场景时,应禁用排序行为,并使用节点的zPositions创建确定性的场景顺序。

重要信息
SKCropNodeSKEffectNode节点类可更改场景渲染行为。 这些节点的子级被独立渲染为单独的节点树,结果被渲染到包含作物或效果节点的树中。 有关更多信息,请参见使用其他节点类型。

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;

设置节点的xScaleyScale属性

xScale

比例因子,乘以节点及其子节点的宽度。

@property(nonatomic) CGFloat xScale;

xScale属性缩放节点及其所有子类的宽度。 比例值会影响节点框架的计算方式,命中测试区域,绘制方式以及其他类似特征。 默认值为1.0。

yScale

比例因子,乘以节点及其子节点的宽度。

@property(nonatomic) CGFloat yScale;

yScale属性缩放节点及其所有后代的高度。 比例值会影响节点框架的计算方式,命中测试区域,绘制方式以及其他类似特征。 默认值为1.0。


访问相关节点

了解节点如何遵循其坐标系。

总览

将节点放置在节点树中时,其位置属性会将其放置在其父级提供的坐标系内。 SpriteKitiOSmacOS上使用相同的坐标系。

点作为位置单位

将节点放置在节点树中时,其位置属性会将其放置在其父级提供的坐标系内。 SpriteKitiOSmacOS上使用相同的坐标系。 下图显示了SpriteKit坐标系。 与UIKitAppKit一样,坐标值以点为单位。 必要时,渲染场景时将点转换为像素。 x坐标正向右移,y坐标正向屏幕上移。

SpriteKit坐标系统

极坐标旋转(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()
相关属性

scene
parent
children

修改和访问节点树

您可以通过在节点之间创建父子关系来创建节点树。 每个节点维护一个有序的子级列表,通过读取节点的children属性来引用该列表。 树中子级的顺序会影响场景处理的许多方面,包括命中测试和渲染,因此适当地组织节点树很重要。

- addChild:
- insertChild:atIndex:`
- isEqualToNode:
如果该节点是父节点的后代,则为YES;否则为。 否则没有。

- moveToParent:
- removeFromParent
- removeAllChildren
- removeChildrenInArray:
- inParentHierarchy:

自定义节点

Customizing the Behavior of a Node

向子类传播属性(修改后会影响子节点的属性)

按名称访问节点

通常组织场景树中的节点来确定场景的精确渲染顺序,而不是确定这些节点在场景中扮演的角色。 您可能还从存档文件加载节点,而不是在运行时直接实例化它们,因此您可能需要能够在节点树中找到特定的节点。 为此,您需要为节点提供名称,然后搜索这些名称。

节点名称通常在您的应用程序中有两个作用:

  1. 您可以编写自己的代码,以根据节点名称实现游戏逻辑。 例如,当两个物理对象碰撞时,可以使用节点名称来确定碰撞如何影响游戏玩法。
  2. 您可以搜索具有特定名称的节点。 通常,第一次加载场景时,只需执行一次。
为节点起名

节点的名称属性应为字母数字字符串,且不带标点符号。 以下代码显示了如何命名三个不同的节点以将它们彼此区分开。

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 搜索仅匹配指定的字符。

官方示例:


更改节点可见性

alpha

hidden

Running Actions

在Sprite上创建、配置和运行Actions

总览

当您要为场景的内容设置动画时,您告诉节点运行SKAction的实例。 当场景处理动画帧时,将执行actions。 一些actions在动画的单个帧中完成,而其他actions则在完成之前将更改应用于动画的多个帧。 操作最常见的用途是为节点的属性设置动画。 例如,您可以创建移动节点,缩放或旋转节点或淡化其透明度的actions。 但是,actions也可以更改节点树,播放声音甚至执行自定义代码。

未完。。待续

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容