OctopusKit OctopusKit Logo

一个基于 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。

QuickStart Demo

  1. 示例
  2. 概述
  3. 设计目标
  4. 入门指南
  5. 等等

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 使用 “实体-组件-系统” 架构,其中

有关对象层次结构的详细分解,请参阅 架构文档

您的主要工作流程将是为图形和游戏玩法的每个“部分”编写组件类,然后将它们组合起来构建出现在屏幕上的实体,或处理“后端”数据的抽象实体。

例如,假设一个 ParallaxBackgroundEntity 包含一个 CloudsComponent、一个 HillsComponent 和一个 TreesComponent,或者一个 GameSessionEntity 包含一个 WorldMapComponent 和一个 MultiplayerSyncComponent

性能: 尽管尚未进行广泛的基准测试,但 OK 可以在 iPhone XS 上以每秒 60 帧的速度显示超过 5000 个精灵; 每个精灵都由一个实体表示,该实体具有多个组件,这些组件每帧都会更新并响应触摸输入。

设计目标

入门指南

  1. 阅读 快速入门使用指南。 您将需要 Xcode 12、iOS 14 和 macOS Big Sur(尽管 OK 可能在旧版本上通过一些手动修改工作。)

    技能水平:中级:虽然 OK 没有以专为绝对初学者设计的方式呈现,主要是因为我太懒了,无法从零开始编写文档,但它也不是“高级”水平的东西; 如果您阅读过 Swift 语言书 并尝试在 Xcode 中制作 SpriteKit 游戏,那么您就可以使用 OK 了!

    如果您还不熟悉 “组合优于继承”“实体-组件-系统” 模式,您也应该阅读相关内容,尽管 OK 对这些模式的实现可能与您期望的不同。

    另请参阅 Apple 的 SwiftUI 教程。

  2. 有关引擎架构的详细概述,请参阅 架构

  3. 卡住了? 请参阅 提示和故障排除

  4. 想知道某些事情是否是故意这样做,或者为什么? 编码约定和设计决策 可能会有解释。

  5. 想密切关注即将推出的功能或帮助开发缺失的功能? 请参阅 TODO 和路线图

等等


OctopusKit © 2023 Invading OctopusApache 许可证 2.0