一个基于 ECS 并使用 100% Swift 编写的 2D 游戏引擎,适用于 iOS、macOS、tvOS 和 visionOS。
注意
😔 此项目已不再由唯一维护者更新。
🌟 我已转向 Godot。 查看 Comedot ← 我在 Godot 中用于 2D 游戏的基于组件的框架!
重要提示
❕ OctopusKit 需要 OctopusCore。 非游戏功能已拆分到单独的仓库中,以便在通用应用程序中使用。 对于最后一个独立版本,请参阅 4.0.0-beta-5
如果您尝试过在坚持官方 API 的同时用 Swift 制作游戏,这可能适合您! OctopusKit 封装并扩展了 Apple 的框架
• GameplayKit,用于灵活的实体-组件-系统架构,以动态组合游戏行为。
• SpriteKit,用于 2D 图形、物理和 GPU 着色器。
• SwiftUI,用于使用声明式语法快速设计流畅、可扩展的 HUD。
• Metal,以确保底层最佳原生性能。
• 操作系统独立的组件使您可以使用相同的代码处理鼠标/触摸或键盘/游戏手柄输入,并为 iOS + macOS 本地编译,而无需 Catalyst。
OctopusKit 是一个持续进行中的工作,我仍在不断学习,因此它可能会快速更改,恕不另行维护向后兼容性或更新文档。
此项目是我尝试用纯 Swift 制作游戏的结果。 我爱上了这门语言,但找不到任何支持它的引擎,也没有找到我直观的架构,所以我开始制作自己的引擎。
欢迎反馈! – ShinryakuTako
🚀 渴望深入了解? 将 OctopusKit 作为 Swift Package Manager 依赖项添加到 SwiftUI 项目中,并使用 快速入门 模板(它也充当一个小演示。)
🎨 与 SwiftUI 一起使用
import SwiftUI
import OctopusKit
struct ContentView: View {
// The coordinator object manages your game's scenes and global state.
@StateObject var gameCoordinator = OKGameCoordinator(states: [
MainMenu(),
Lobby(),
Gameplay() ])
var body: some View {
// The container view combines SpriteKit with SwiftUI,
// and presents the coordinator's current scene.
OKContainerView()
.environmentObject(gameCoordinator)
.statusBar(hidden: true)
}
}
👾 创建动画精灵
var character = OKEntity(components: [
// Start with a blank texture.
NodeComponent(node: SKSpriteNode(color: .clear, size: CGSize(widthAndHeight: 42))),
// Load texture resources.
TextureDictionaryComponent(atlasName: "PlayerCharacter"),
// Animate the sprite with textures whose names begin with the specified prefix.
TextureAnimationComponent(initialAnimationTexturePrefix: "Idle") ])
🕹 添加玩家控制
// Add a component to the scene that will be updated with input events.
// Other components that handle player input will query this component.
// This lets us handle asynchronous events in sync with the frame-update cycle.
// A shared event stream is more efficient than forwarding events to every entity.
// PointerEventComponent is an OS-agnostic component for touch or mouse input.
let sharedPointerEventComponent = PointerEventComponent()
scene.entity?.addComponent(sharedPointerEventComponent)
character.addComponents([
// A relay component adds a reference to a component from another entity,
// and also fulfills the dependencies of other components in this entity.
RelayComponent(for: sharedPointerEventComponent),
// This component checks the entity's PointerEventComponent (provided here by a relay)
// and syncs the entity's position to the touch or mouse location in every frame.
PointerControlledPositioningComponent() ])
🕹 动态移除玩家控制或更改为不同的输入方法
character.removeComponent(ofType: PointerControlledPositioningComponent.self)
character.addComponents([
// Add a physics body to the sprite.
PhysicsComponent(),
RelayComponent(for: sharedKeyboardEventComponent),
// Apply a force to the body based on keyboard input in each frame.
KeyboardControlledForceComponent() ])
🧩 自定义特定于游戏的组件
class AngryEnemyComponent: OKComponent, RequiresUpdatesPerFrame {
override func didAddToEntity(withNode node: SKNode) {
node.colorTint = .angryMonster
}
override func update(deltaTime seconds: TimeInterval) {
guard let behaviorComponent = coComponent(EnemyBehaviorComponent.self) else { return }
behaviorComponent.regenerateHP()
behaviorComponent.chasePlayerWithExtraFervor()
}
override func willRemoveFromEntity(withNode node: SKNode) {
node.colorTint = .mildlyInconveniencedMonster
}
}
🛠 使用自定义闭包根据玩家移动更改动画
// Add a component that executes the supplied closure every frame.
character.addComponent(RepeatingClosureComponent { component in
// Check if the entity of this component has the required dependencies at runtime.
// This approach allows dynamic behavior modification instead of halting the game.
if let physicsBody = component.coComponent(PhysicsComponent.self)?.physicsBody,
let animationComponent = component.coComponent(TextureAnimationComponent.self)
{
// Change the animation depending on whether the body is stationary or mobile.
animationComponent.textureDictionaryPrefix = physicsBody.isResting ? "Idle" : "Moving"
}
})
// This behavior could be better encapsulated in a custom component,
// with many different game-specific animations depending on many conditions.
🎎 加载在 Xcode Scene Editor 中构建的场景,并从共享名称标识的精灵创建多个实体
// Load a ".sks" file as a child node.
if let editorScene = SKReferenceNode(fileNamed: "EditorScene.sks") {
scene.addChild(editorScene)
}
// Search the entire tree for all nodes named "Turret",
// and give them properties of "tower defense" turrets,
// and make them independently draggable by the player.
for turretNode in scene["//Turret"] {
// Create a new entity for each node found.
scene.addEntity(OKEntity(components: [
NodeComponent(node: turretNode),
RelayComponent(for: sharedPointerEventComponent),
// Hypothetical game-specific components.
HealthComponent(),
AttackComponent(),
MonsterTargetingComponent(),
// Track the first touch or mouse drag that begins inside the sprite.
NodePointerStateComponent(),
// Let the player select and drag a specific sprite.
// This differs from the PointerControlledPositioningComponent in a previous example,
// which repositions nodes regardless of where the pointer began.
PointerControlledDraggingComponent() ]))
}
// Once the first monster wave starts, you could replace PointerControlledDraggingComponent
// with PointerControlledShootingComponent to make the turrets immovable but manually-fired.
OctopusKit 使用 “实体-组件-系统” 架构,其中
🎬 游戏被组织成状态,例如 MainMenu、Playing 和 Paused。 每个状态都与一个 SwiftUI 视图相关联,该视图显示用户界面,以及一个 SpriteKit 场景,该场景使用 实体、组件 和 系统 呈现该状态的游戏玩法。
您可以根据需要将游戏划分为任意数量的状态。 例如,一个“PlayState”也处理主菜单、暂停、过场动画等。
状态、场景和 SwiftUI 视图可能具有多对多的关系,这些关系可能在运行时更改。
👾 实体 只是 组件 的集合。 它们不包含任何逻辑,除了用于初始化相关组件组的便捷构造函数。
🧩 组件(也可以称为行为、效果、功能或特征)是 OctopusKit 中的核心概念,包含构成游戏每个视觉或抽象元素的属性以及逻辑*。 组件在其添加到实体时、帧更新时和/或从实体移除时运行其代码。 组件可以查询其实体的其他组件,并影响彼此的行为,以在运行时形成动态依赖关系。 该引擎附带一个可自定义组件库,用于图形、游戏玩法、物理等。
⛓ 系统 只是 特定类 的组件集合。 它们不执行任何逻辑*,但它们由 场景 在数组中排列,以在每个帧中以确定性顺序执行来自所有实体的组件,以便依赖于其他组件的组件在其依赖项之后更新。
* 这些定义可能与其他引擎(如 Unity)不同,在 Unity 中,所有逻辑都包含在系统中。
🎛 用户界面 元素(如按钮、列表和 HUD)在 SwiftUI 中设计。 这允许流畅的动画、清晰的文本、矢量形状、实时预览、自动数据驱动的更新,以及来自 Apple SF Symbols 的 1,500 多个高质量图标。
有关对象层次结构的详细分解,请参阅 架构文档。
您的主要工作流程将是为图形和游戏玩法的每个“部分”编写组件类,然后将它们组合起来构建出现在屏幕上的实体,或处理“后端”数据的抽象实体。
例如,假设一个 ParallaxBackgroundEntity 包含一个 CloudsComponent、一个 HillsComponent 和一个 TreesComponent,或者一个 GameSessionEntity 包含一个 WorldMapComponent 和一个 MultiplayerSyncComponent。
性能: 尽管尚未进行广泛的基准测试,但 OK 可以在 iPhone XS 上以每秒 60 帧的速度显示超过 5000 个精灵; 每个精灵都由一个实体表示,该实体具有多个组件,这些组件每帧都会更新并响应触摸输入。
为 Swift 量身定制:Swift,Swift,Swift! 该框架必须遵循 Swift API 设计的 既定指南。 一切都必须在 Swift 中有意义,并尽可能与 Swift 习惯用法无缝衔接。
Vitamin 2D:目前,OK 主要是一个用于 2D 游戏的框架,但它并不阻止您使用 SceneKit 或低级 Metal 视图等技术,它也可以用于非游戏应用程序。
站在巨人的肩膀上:该引擎利用了 Apple 提供的 SpriteKit、GameplayKit、SwiftUI 和其他技术。 它不应试图“对抗”它们、替换它们或将它们隐藏在过多的抽象层之后。
OK 主要通过 SpriteKit 和 GameplayKit 类的自定义子类和扩展来实现,而不会“模糊”它们或阻止您与基类交互。 这使您可以逐步采用此框架,并让您将游戏与 Xcode IDE 工具(例如 Scene Editor)集成在一起(如果可能)。
与 Apple API 的紧密耦合也确保了您的游戏具有面向未来的特性; 每当 Apple 改进这些框架时,OctopusKit 和您的游戏也应该“免费”获得一些好处。 例如,当 Metal 推出时,SpriteKit 进行了更新,以自动在底层使用 Metal 而不是 OpenGL,从而为许多现有游戏带来了性能提升。 (WWDC 2016,Session 610)
代码优先:OK 主要是一个“编程”引擎; 几乎所有事情都在代码中完成。 这也有助于源代码控制。 Xcode Scene Editor 由于其不完整性和错误(截至 2018 年 5 月,Xcode 9.4)而被降级为“二等公民”地位,但在方便的情况下仍受支持。 请参阅下一点。
💡 您可以在 Scene Editor 中设计高级布局/模型,使用带有名称(标识符)的占位符节点。 然后,您可以从这些节点创建实体,并在代码中向其添加组件。
现在有了 SwiftUI,Apple 平台的编程正朝着关注代码而不是可视化编辑器的方向发展。
可定制性和灵活性:该引擎力求灵活,并让您可以自由地以各种方式构建游戏。 由于您可以完全访问引擎的源代码,因此您可以修改或扩展任何内容以适应每个项目的确切需求。
您可以使用以下任何方法来构建场景,按引擎支持的顺序排列
- 主要在代码中执行节点的创建和放置。 很少使用 Xcode Scene Editor,用于设计和预览一些单独的元素,例如具有特定位置等的实体,而不是整个场景,并使用
SKReferenceNode
在代码中加载它们。
- 使用 Xcode Scene Editor 作为您的起点,创建可以作为顶级
SKReferenceNode
OKScene
实例加载的模板场景。 这种方法允许少量“所见即所得”的可视化设计和预览。
- 几乎完全在 Xcode Scene Editor 中创建场景,在 IDE 中添加任何受支持的组件、操作、物理体、导航图和纹理等。
将场景的自定义类设置为OKScene
或其子类。 通过调用OKViewController.loadAndPresentScene(fileNamed:withTransition:)
加载场景,例如在OKGameState
的didEnter.from(_:)
事件期间。
- 您不必使用此处建议的任何架构和模式; 您不必使用游戏状态,并且您的游戏对象甚至不必从任何 OK 类继承。 您可以使用自己的架构,并且仅将 OK 用于一些辅助方法等,仅保留此框架中您需要的内容,并从编译中排除其余部分。
自包含:如果您的项目不需要任何其他第三方库,您应该不需要下载或跟上任何其他第三方库; OK 使用的所有内容都在 OK 或 Apple 框架内,因此它可以开箱即用。
阅读 快速入门 和 使用指南。 您将需要 Xcode 12、iOS 14 和 macOS Big Sur(尽管 OK 可能在旧版本上通过一些手动修改工作。)
技能水平:中级:虽然 OK 没有以专为绝对初学者设计的方式呈现,主要是因为我太懒了,无法从零开始编写文档,但它也不是“高级”水平的东西; 如果您阅读过 Swift 语言书 并尝试在 Xcode 中制作 SpriteKit 游戏,那么您就可以使用 OK 了!
如果您还不熟悉 “组合优于继承” 和 “实体-组件-系统” 模式,您也应该阅读相关内容,尽管 OK 对这些模式的实现可能与您期望的不同。
另请参阅 Apple 的 SwiftUI 教程。
有关引擎架构的详细概述,请参阅 架构。
卡住了? 请参阅 提示和故障排除。
想知道某些事情是否是故意这样做,或者为什么? 编码约定和设计决策 可能会有解释。
想密切关注即将推出的功能或帮助开发缺失的功能? 请参阅 TODO 和路线图。
贡献者和支持者 ❤︎
此项目可能被称为 OctopusKit、“OK”或“OKIO”(代表“Invading Octopus 的 OctopusKit”),但“IOOK”听起来很奇怪。
命名灵感来自 Rogue Amoeba 等公司、.io 域名和动漫 侵略!花枝娘。
示例部分中最后一个 ])
之前的空格是为了清晰起见。 :)
许可证:Apache 2.0
整合了来自 ShaderKit 的着色器 © Paul Hudson,根据 MIT 许可证获得许可(请参阅相关文件中的标头)。
告诉 我一切有多棒或多糟糕:Discord、Twitter 或 🅾ctopus🅺it@🅘nvading🅞ctopus.ⓘⓞ
但我很少查看这些,所以提出问题的最佳方式可能是通过在 GitHub 存储库上打开一个 issue。
支持 我颓废的生活方式,这样我就可以专注于制作卖不出去的东西:我的 Patreon
此项目与 Apple 没有任何关联。
OctopusKit © 2023 Invading Octopus • Apache 许可证 2.0