SkillAgentSearch skills...

Octopuskit

2D ECS game engine in 100% Swift + SwiftUI for iOS, macOS, tvOS

Install / Use

/learn @InvadingOctopus/Octopuskit

README

OctopusKit OctopusKit Logo

A 2D game engine based on ECS and written in 100% Swift for iOS, macOS, tvOS and visionOS.

[!CAUTION] 😔 This project is no longer updated by the sole maintainer.
🌟 I have moved to Godot. Check out [Comedot][comedot] ← my components-based framework for 2D games in Godot!

[!IMPORTANT] ❕ OctopusKit requires [OctopusCore][octopuscore]. Non-game functionality was split into a separate repository for use in general apps. For the last standalone version, see 4.0.0-beta-5

If you've tried making a game in Swift while sticking to the official APIs, this may be for you! OctopusKit wraps and extends Apple's frameworks:

GameplayKit for a flexible Entity-Component-System architecture to dynamically compose game behavior.
SpriteKit for 2D graphics, physics and GPU shaders.
SwiftUI for quickly designing fluid, scalable HUDs with a declarative syntax.
Metal to ensure the best native performance under the hood.
• OS-independent components let you handle mouse/touch or keyboard/gamepad input with the same code, and compile natively for iOS + macOS without needing Catalyst.

QuickStart Demo

  1. Examples
  2. Overview
  3. Design Goals
  4. Getting Started
  5. Etcetera

OctopusKit is a constant work in progress and I'm still learning as I go, so it may change rapidly without maintaining backwards compatibility or updating the documentation.

This project is the result of my attempts to make games in pure Swift. I fell in love with the language but couldn't find any engines that supported it or had the kind of architecture that I found intuitive, so I started making my own.

Feedback welcome! – ShinryakuTako

Examples

🚀 Eager to dive in? Add OctopusKit as a Swift Package Manager dependency to a SwiftUI project, and use the [QuickStart template][quickstart] (which also serves as a little demo.)

🎨 Using with 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)
    }
}

👾 Creating an animated sprite

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") ])

🕹 Adding player control

// 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() ])

🕹 Dynamically removing player control or changing to a different input method

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() ])

🧩 A custom game-specific component

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
    }
}

🛠 Using a custom closure to change the animation based on player movement

// 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.

🎎 Loading a scene built in the Xcode Scene Editor and creating multiple entities from sprites identified by a shared name

// 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.

Overview

OctopusKit uses an ["Entity-Component-System"][entity–component–system] architecture, where:

  • 🎬 A game is organized into States such as MainMenu, Playing and Paused. Each state is associated with a SwiftUI view which displays the user interface, and a SpriteKit Scene that presents the gameplay for that state using Entities, Components and Systems.

    You can divide your game into as many or as few states as you want. e.g. A single "PlayState" which also handles the main menu, pausing, cutscenes etc.

    States, Scenes, and SwiftUI views may have many-to-many relationships that may change during runtime.

  • 👾 Entities are simply collections of Components. They contain no logic, except for convenience constructors which initialize groups of related components.

  • 🧩 Components (which could also be called Behaviors, Effects, Features, or Traits) are the core concept in OctopusKit, containing the properties as well as the logic* which make up each visual or abstract element of the game. A component runs its code when it's added to an entity, when a frame is updated, and/or when it's removed from an entity. Components may query their entity for other components and affect each other's behavior to form dynamic dependencies during runtime. The engine comes with a library of customizable components for graphics, gameplay, physics etc.

  • Systems are simply collections of components of a specific class. They don't perform any logic*, but they're arranged by a Scene in an array to execute components from all entities in a deterministic order every frame, so that components which rely on other components are updated after their dependencies.

    * These definitions may differ from other engines, like Unity, where all the logic is contained within systems.

  • 🎛 User Interface elements like buttons, lists and HUDs are designed in SwiftUI. This allows fluid animations, sharp text, vector shapes, live previews, automatic data-driven updates, and over 1,500 high-quality icons from Apple's [SF Symbols.][sf-symbols]

See the [Architecture documentation][architec

Related Skills

View on GitHub
GitHub Stars483
CategoryDevelopment
Updated1mo ago
Forks31

Languages

Swift

Security Score

100/100

Audited on Feb 25, 2026

No findings