HLSpriteKit 2.1.0

HLSpriteKit 2.1.0

测试已测试
语言语言 Obj-CObjective C
版权许可 MIT
发布上次发布2024年7月

Karl Voskuil 维护。



  • 作者
  • Karl Voskuil

HLSpriteKit

Version Platform License

SpriteKit 场景和节点子类,以及各种实用工具。

功能

HLLayoutManager

布局管理器提供单个方法(layout)来布局节点。它可以使用 SKNode+HLLayoutManager 类别附加到任何 SKNode

目前提供的布局管理器

  • HLTableLayoutManager 用于类似表格的布局;

  • HLGridLayoutManager 用于类似网格的布局;

  • HLStackLayoutManager 用于一维布局;

  • HLRingLayoutManager 用于类似环的极坐标布局;

  • HLOutlineLayoutManager 用于垂直列表(特别是文本)和有缩进级别的。

  • HLParallaxLayoutManager 用于以不同速度移动的节点层。

将布局代码放在第三方对象中(而不是放在 SKSceneSKNode 子类中)可以使常用布局数学更易于重用。

自定义 SKNode 子类

HLSpriteKit 包含了许多自定义 SKNode 子类。

  • HLGridNode。以相同大小的正方形网格组织内容,包含视觉格式化和交互选项。

  • HLLabelButtonNode。在 SKSpriteNode 上显示一个简单的 SKLabelNode,但具有额外的尺寸和居中对齐选项。特别是,它可以调整精灵节点的尺寸以适应文本,并且可以实现基线对齐,使得整个字体大小(包括下降部分)垂直居中在背景中;对于类别 SKLabelNode+HLLabelNodeAdditions 中的所有 SKLabelNode,都提供了计算数学。

  • HLMenuNode。按钮层次菜单的接口和模型。接口是一个简单的垂直堆叠按钮,但目前它提供了一些布局和动画功能。

  • HLMessageNode。在固体或纹理背景上显示一条文本消息,并包含一些动画选项。

  • HLMultilineLabelNode。一个可以显示多行文本的标签节点。

  • HLRingNode。在中心点周围排列一系列项目(通常是按钮)。

  • HLScrollNode。支持通过缩放手势和滚动手势进行滚动和缩放内容。接口故意与 UIScrollView 类似。

  • HLTiledNode。像 SKSpriteNode 一样平铺其纹理以适应指定的尺寸。

  • HLToolbarNode。具有各种视觉格式化、尺寸和动画选项的水平工具栏,由正方形组成。

HLGestureTarget

手势目标处理来自手势识别器的手势(在 iOS 下为 UIGestureRecognizer,在 macOS 下为 NSGestureRecognizer)。它可以使用类分类 SKNode+HLGestureTarget附加到任何 SKNode 上。

使用模式是这样的:SKScene 了解其视图,因此场景是手势识别器的委托。它管理共享手势识别器的集合,根据需要将其附加到视图并将其从视图中分离。当手势识别器识别出某个手势时,场景会了解哪个节点或哪些节点是手势的目标,并使用 HLGestureTarget 接口将这些手势转发给这些节点。

关键是:场景可以有效地使用手势识别器(而不是响应者接口 touchesBegan:withEvent:mouseUp:),并且手势处理代码可以封装在节点子类中(而不是将它们放入臃肿的场景中)。

HLScene

HLScene 包含许多场景常用的功能,包括但不限于:

  • 在后台线程中加载场景资源
  • 一个共享的手势识别系统和 HLGestureTarget 感知的手势代理实现
  • 在场景上方呈现模态节点
  • 注册节点以实现常见的场景相关行为(例如:场景缩放时调整大小;编码时不编码等)

HLAction

HLAction 提供了针对 SKAction 系统的一种状态化的替代方案。HLAction 的主要使用场景是在编码期间持久化动画状态并在解码时恢复。

以下是一个示例来说明这一点。

假设在游戏中每当一只兽人死亡时,都会运行一个 SKAction 序列:首先,兽人节点因为纹理动画而颤抖并跌倒;然后,兽人节点在 3 秒内缓慢淡出;最后,将兽人节点从场景中移除。

假设在这样的死亡淡入过程中,用户将应用置于后台(或保存游戏),并对其进行编码。

当游戏重新开始(并解码)时,最好是如果兽人尸体继续淡出。更好的是,它应该与 iOS 在应用状态保存期间捕获的截图像素级匹配。

使用 SKAction 系统难以达到这样的精确度。以下是一些常见可能性:

  • 如果使用 NSCoding 对整个兽人节点进行编码,那么正在进行的动画序列将能够成功编码、解码并恢复。不过,遗憾的是,它并没有编码其进度或其原始状态,因此在没有重置 alpha 的情况下从序列的开始重新开始。在示例中,这意味着部分淡出的兽人将再次跳跃,重新开始颤抖和跌倒。

  • 如果不对兽人节点进行编码,而是在恢复时重新创建,那么 SKAction 动画序列将无法恢复。兽人节点要么会消失,要么会无限期地保持原地不动,或者应用必须找出如何保留和恢复动画序列的状态。

HLAction 通过只将所有动画的状态表示为与特定节点松散耦合的可编码对象,来解决此问题。

HLAction 的主要缺点是您无法在 SKNode 中使用 SKAction 运行循环,并且所提供的行为功能不如完整。

正在根据需要添加新行为。如果您需要尚未添加的行为,请告诉我。

HLHacktion

HLHacktion 为各种目的提供了 SKAction 的替代方案。

名称中的 hack 避免了与 HLAction 的命名冲突,并且承认这些解决方案是在现有的 SKAction 系统之上构建。有关 SKAction 的独立于它的替代系统,请参阅 HLAction

HLHacktion 的一个例子:[HLHacktion performSelector:onWeakTarget:] 返回一个保留弱目标的 SKAction,这样可以避免由 [SKAction performSelector:onTarget:] 导致的保留循环。

HLHacktion 还提供了可编码的块运行 SKAction 行为的替代方案。问题是这样的:当对 SKScene 节点层次结构进行编码时,这在应用程序状态保存或“游戏保存”期间很常见,必须特别处理运行 SKAction 行为并带有代码块的节点,因为这些代码块不能进行编码。特别是,尝试编码 runBlock:customActionWithDuration:actionBlock: 将导致运行时警告消息

SKAction:运行块行为不能正确编码,Objective-C 块不支持 NSCoding。

HLHacktion 行为提供的解决方案是使用选择器回调(带额外功能)而不是代码块。

手势识别常见问题及示例

我想在我的场景中使用 UIGestureRecognizerNSGestureRecognizer 来识别手势。

这里是在 HLSpriteKit 中使用的模式:

  • 您的场景拥有与场景相关的所有手势识别器对象。作为在 SKView 上显示的,它会将手势识别器添加到视图中。

  • 您的场景是手势识别器的代理;即 UIGestureRecognizerDelegateNSGestureRecognizerDelegate

  • 在手势识别器开始识别之前,您的场景将手势识别器的目标(对象和选择器)设置为最相关的接收节点。随着手势的识别,该节点将获得调用。

考虑一些替代设计方案。特别是,如果你的场景包含多个按钮节点,它们应该响应触摸事件。这些设计方案 不包括HLSpriteKit 中使用的模式。

  • 每个按钮都可以向 SKView 添加自己的 UITapGestureRecognizer

  • 按钮可以共享一个单个的 UITapGestureRecognizer,该手势识别器在场景中有固定的目标方法;称之为 handleTap:。当识别到触摸手势时, handleTap: 会确定哪个按钮被触摸,并执行相应的代码。

  • 按钮可以共享单个的 UITapGestureRecognizer,并且每个都向其添加一个单独的目标。当识别到触摸手势时,每个目标都会决定是否被触摸,如果是,则执行相应的代码。

我想以 HLSpriteKit 方式在我的场景中使用手势识别器。

将您的场景创建为 HLScene 的子类。

我想在我的场景中使用您的一些手势目标组件,例如 HLToolbarNode

HLSpriteKit 中的组件都是手势目标,但默认情况下该机制是禁用的。只需几行代码即可启用。

首先,确保您是 HLScene 的子类

#import "HLSpriteKit/HLSpriteKit.h"

@interface MyScene : HLScene

接下来,创建您的 HLToolbarNode 并将其添加到场景中

HLToolbarNode *toolbarNode = ...;
toolbarNode.delegate = self;
[self addChild:toolbarNode];

最后,将工具栏的手势目标设置为自身,并通知场景它需要创建一些适当的的手势识别器

[toolbarNode hlSetGestureTarget:toolbarNode];
[self needsSharedGestureRecognizersForNode:toolbarNode];

这样将为工具栏工具的触摸或点击提供委托回调。

请参阅示例项目(项目中的 HLSpriteKit/Example/HLSpriteKit/HLCatalogScene.mGitHub 上的示例)以获得使用多个手势目标的场景的运行示例。

我想在我的场景中创建自己的文果target节点。

好的!

这里有一些选项

  1. 创建一个可以自己作为文果target的自定义节点。

  2. 将一个通用文果target附加到现有节点。

  3. 将一个自定义文果target附加到现有节点。

  4. 在场景中处理。

创建一个可以自己作为文果target的自定义节点。

遵循HLSpriteKit中组件的模式,并在自定义节点类中遵守HLGestureTarget协议。通过HLGestureTarget接口,您的节点将告诉其场景它期望的文果识别器,以及当这些文果识别器被触发时要做什么。

然后,您可以像包含HLSpriteKit中的文果target组件一样包含您的节点。

将一个通用文果target附加到现有节点。

有时创建一个新的节点类可能有些过度。有时甚至实现代理接口也显得有些过度。这里有一些例子

  • 您在场景中有一个红色的正方形精灵节点,您想让它在被点击时摆动。

  • 您想弹出一个带有一些文本的标签节点,并且点击它时消失。

或者这里有个不同的问题:比如说您从一个第三方库中获得了一个现成的节点类,它没有任何交互程序,而您想让它对点击做出反应。

对于所有这些问题,您可以无需创建任何新类,将一个通用文果target附加到现有节点。

以下是在假设您的场景是HLScene的子类的情况下,使点击时红色的正方形精灵节点摆动的代码

SKSpriteNode *redSquareNode = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(20.0f, 20.0f)];
[self addChild:redSquareNode];
HLTapGestureTarget *tapGestureTarget = [[HLTapGestureTarget alloc] init];
tapGestureTarget.handleGestureBlock = ^(UIGestureRecognizer *gestureRecognizer){
  // wiggle red square node
};
[redSquareNode hlSetGestureTarget:tapGestureTarget];
[self needsSharedGestureRecognizersForNode:redSquareNode];

HLTapGestureTarget是一个简单的手势目标实现,它只认识点击手势(而非滑动或长按)。因为点击手势非常直接,所以很容易将同一个手势目标重复用于任何节点。

弹出示例

HLLabelButtonNode *labelButtonNode = [[HLLabelButtonNode alloc] initWithColor:[SKColor blackColor] size:CGSizeZero];
labelButtonNode.automaticWidth = YES;
labelButtonNode.automaticHeight = YES;
labelButtonNode.text = @"Tap to dismiss";
[self addChild:labelButtonNode];

__weak HLLabelButtonNode *labelButtonNodeWeak = labelButtonNode;
[labelButtonNode hlSetGestureTarget:[HLTapGestureTarget tapGestureTargetWithHandleGestureBlock:^(UIGestureRecognizer *gestureRecognizer){
  [labelButtonNodeWeak removeFromParent];
}]];
[self needsSharedGestureRecognizersForNode:labelButtonNode];

HLLabelButtonNode甚至没有实现自己的手势目标,因为通用的HLTapGestureTarget通常是所有者想要的。因此,这段代码也可以作为将通用手势目标附加到第三方节点的示例。

将自定义手势目标附加到现有节点。

HLToolbarNode可以识别点击,但不能识别长按。你能处理长按吗?也许还能处理滑动呢?

你可以编写自己的自定义HLGestureTarget,并使用熟悉的SKNode类别扩展hlSetGestureTarget将其附加到节点。

我这样做是一个练习,发现它不太愉快。练习的结果是HLToolbarNode.h中声明的类HLToolbarNodeMultiGestureTarget。一旦编写,启用代码就变得熟悉

HLToolbarNode *toolbarNode = ...;
toolbarNode.delegate = self;
[self addChild:toolbarNode];

HLToolbarNodeMultiGestureTarget *multiGestureTarget = [[HLToolbarNodeMultiGestureTarget alloc] initWithToolbarNode:toolbarNode];
multiGestureTarget.delegate = self;
[toolbarNode hlSetGestureTarget:multiGestureTarget];
[self needsSharedGestureRecognizersForNode:toolbarNode];

你可以使用HLToolbarNodeMultiGestureTarget类作为编写自定义手势目标的模板。

在设计阶段,为任何节点编写自定义手势目标的能力似乎是HLGestureTarget系统的优势之一。例如,它可以防止默认的HLToolbarNode手势目标出现臃肿。但在实践中,似乎要花费很多精力才能从场景中移除手势处理代码,然后再将其委托回场景。

可能更好的设计选择是子类化HLToolbarNode以覆盖默认的手势处理。或者直接在场景中处理手势,而不是在手势目标中。

在场景中处理。

HLGestureTarget的一个目标是将手势处理代码从场景中提取出来,以便它可以在场景之间更容易地重用。

但在这里我们遇到了。你希望在场景中处理一些手势。

你可以在Flippy的一个场景中看到一个这种混合模型示例。搜索臃肿的方法gestureRecognizer:shouldReceiveTouch:。该场景大多数手势是在行内处理的,但有时会调用[super]HLScene处理真正的HLGestureTarget组件。这里有一个摘录

// Modal overlay layer (handled by HLScene).
if ([self modalNodePresented]) {
  return [super gestureRecognizer:gestureRecognizer shouldReceiveTouch:touch];
}

// Construction toolbar.
if (_constructionToolbarState.toolbarNode
    && _constructionToolbarState.toolbarNode.parent
    && [_constructionToolbarState.toolbarNode containsPoint:sceneLocation]) {
  if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
    [gestureRecognizer removeTarget:nil action:NULL];
    [gestureRecognizer addTarget:self action:@selector(handleConstructionToolbarPan:)];
    return YES;
  }
  if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]
      && [(UITapGestureRecognizer *)gestureRecognizer numberOfTapsRequired] == 1) {
    [gestureRecognizer removeTarget:nil action:NULL];
    [gestureRecognizer addTarget:self action:@selector(handleConstructionToolbarTap:)];
    return YES;
  }
  if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
    [gestureRecognizer removeTarget:nil action:NULL];
    [gestureRecognizer addTarget:self action:@selector(handleConstructionToolbarLongPress:)];
    return YES;
  }
  return NO;
}

...

注意重写的方式与 HLScene 相同的模式:如果某个组件应该获取手势,则清除旧的对手势的目标并设置一个新的目标。

我想在我的场景中使用您的一些手势目标组件,例如 HLToolbarNode,但我不想使用您的手势处理系统。

HLGestureTarget 轻量级,可选,不应为 HLSpriteKit 组件引入额外的开销。

我已经为一些组件编写了非常简单的 UIResponderNSResponder 用户交互实现作为概念证明,但我自己并没有使用很多。我很乐意进一步工作,或接受拉取请求。告诉我你需要什么!

开发

HLSpriteKit 正在进行开发,因此包含了其他足够通用的实验性类和函数,适用于重用。例如,包括了一些 SKEmitterNode 存储和一些图像处理函数,但它们是否有用尚不清楚。

安装

# CocoaPods
pod "HLSpriteKit", "~> 2.0"

# Carthage
github "hilogames/HLSpriteKit" ~> 2.0

作者

Karl Voskuil (karl * hilogames dot com)

许可证

HLSpriteKit 目前在 MIT 许可下可用。请参阅 LICENSE 文件了解详细信息。