A flexible composition-based game engine written in Swift for iOS, macOS and tvOS.
Built upon Apple's SpriteKit, GameplayKit and Metal technologies.
- Examples
- Overview
- Design Goals
- Getting Started
- Etcetera (license, contact)
🚀 Eager to dive right in? Download the Quickstart project.
This project is a result of trying to make my own games as a hobby. I love Swift but I couldn't find any engines that support it or had the kind of architecture that I wanted to work with, so I started making my own.
It's also my first ever open-source project and a work in progress; I'm still learning, and OctopusKit may change rapidly without maintaining backwards compatibility. If you have any advice on how to improve the API, coding style, git workflow, or open-source best-practices, I'll be grateful to hear it!
– ShinryakuTako
👾 Creating an animated sprite
let character = OctopusEntity(components: [
// Start with a blank texture.
SpriteKitComponent(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.
// A shared event stream is more efficient than forwarding events to every entity.
let sharedTouchEventComponent = TouchEventComponent()
scene.entity?.addComponent(sharedTouchEventComponent)
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: sharedTouchEventComponent),
// This component checks the entity's TouchEventComponent (provided here by a relay)
// and syncs the entity's position to the touch location in every frame.
TouchControlledPositioningComponent() ])
🕹 Dynamically removing player control or changing to a different input method
character.removeComponents(ofTypes: [
RelayComponent<TouchEventComponent>.self,
TouchControlledPositioningComponent.self])
character.addComponents([
// Add a physics body to the sprite.
PhysicsComponent(),
// Use a hypothetical shared component.
RelayComponent(for: sharedJoystickEventComponent),
// Apply a force to the sprite's body based on joystick input in every frame.
JoystickControlledForceComponent() ])
🛠 Advanced: Using a custom "script" 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(OctopusEntity(components: [
SpriteKitComponent(node: turretNode),
RelayComponent(for: sharedTouchEventComponent),
// Hypothetical game-specific components.
HealthComponent(),
AttackComponent(),
MonsterTargettingComponent(),
// Track the first touch that begins inside the sprite.
NodeTouchComponent(),
// Let the player select and drag a specific sprite.
// This differs from the TouchControlledPositioningComponent in a previous example,
// which repositions nodes regardless of where the touch began.
TouchControlledDraggingComponent() ]))
}
// Once the first monster wave starts, you could replace TouchControlledDraggingComponent
// with TouchControlledShootingComponent to make the turrets immovable but manually-fired.
OctopusKit uses an "Entity-Component-System" architecture, where:
-
🎬 A game is organized into States (such as "MainMenu", "Playing" and "Paused") and Scenes that display the content for those states using Entities, Components and Systems.
-
👾 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. They may be dynamically added to and removed from an entity to alter its appearance and behavior during runtime. The engine comes with a library of many customizable components for graphics, gameplay, physics and UI etc.
-
⛓ Systems are simply collections of components of a specific type. They do not perform any logic*, but they are 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.
OK does not use "data-oriented design", but it does not prevent you from adhering to that in your project.
Your primary workflow will be writing component classes for each "part" of the graphics and gameplay, then combining them to build entities which appear onscreen or abstract entities that handle data on the "backend."
e.g.: say a ParallaxBackgroundEntity containing a CloudsComponent, a HillsComponent and a TreesComponent, or a GameSessionEntity containing a WorldMapComponent and a MultiplayerSyncComponent.
Performance: Although extensive benchmarks have not been done yet, OK can display over 3000 sprites on an iPhone X at 60 frames per second; each sprite represented by an entity with multiple components being updated every frame, and responding to touch input.
-
Tailored for Swift: Swift, Swift, Swift! The framework must follow the established guidelines for Swift API design. Everything must make sense within Swift and flow seamlessly with Swift idioms as much as possible.
-
Vitamin 2D: At the moment, OK is primarily a framework for 2D games, but it does not prevent you from using technologies like SceneKit to render 3D content in 2D space, and it can be used for non-game apps.
-
Shoulders of Giants: The engine leverages SpriteKit, GameplayKit and other technologies provided by Apple. It should not try to "fight" them, replace them, or hide them behind too many abstractions.
OK is mostly implemented through custom subclasses and extensions of the SpriteKit and GameplayKit classes, without "obscuring" them or blocking you from interacting with the base classes. This allows you to adopt this framework incrementally, and lets you integrate your game with the Xcode IDE tools such as the Scene Editor where possible.
Most importantly, the tight coupling with Apple APIs ensures that your game is future-proof; whenever Apple improves these frameworks, OctopusKit and your games should also get some benefits "for free." For example, when Metal was introduced, SpriteKit was updated to automatically use Metal instead of OpenGL under the hood, giving many existing games a performance boost. (WWDC 2016, Session 610)
-
Code Comes First: OK is primarily a "programmatical" engine; almost everything is done in code. This also helps with source control. The Xcode Scene Editor is relegated to "second-class citizen" status because of its incompleteness and bugs as of May 2018 (Xcode 9.4), but it is supported wherever convenient. See the next point.
💡 You can design high-level layouts/mockups in the Scene Editor, using placeholder nodes with names (identifiers.) You may then create entities from those nodes and add components to them in code.
-
Customizability & Flexibility: The engine strives to be flexible and gives you the freedom to structure your game in various ways. Since you have full access to the engine's source code, you can modify or extend anything to suit the exact needs of each project.
You can use any of the following approaches to building your scenes, in order of engine support:
-
Perform the creation and placement of nodes mostly in code. Use the Xcode Scene Editor infrequently, to design and preview a few individual elements such as UI HUDs etc., not entire scenes, and use
SKReferenceNode
to load them in code. -
Use the Xcode Scene Editor as your starting point, to create template scenes that may be loaded as top-level
SKReferenceNode
instances of anOctopusScene
. This approach allows a modicum of "WYSIWYG" visual design and previewing. -
Create a scene almost entirely in the Xcode Scene Editor, adding any supported components, actions, physics bodies, navigation graphs and textures etc. right in the IDE.
Set the custom class of the scene asOctopusScene
or a subclass of it. Load the scene by callingOctopusSceneController.loadAndPresentScene(fileNamed:withTransition:)
, e.g. during thedidEnter.from(_:)
event of anOctopusGameState
. -
You don't have to use any of the architectures and patterns suggested here; you don't have to use game states, and your game objects don't even have to inherit from any OK classes. You could use your own architecture, and just use OK for a few helper methods etc., keeping only what you need from this framework and excluding the rest from compilation.
-
-
Self-Containment: You should not need to download or keep up with any other third-party libraries if your own project does not require them; everything that OK uses is within OK or Apple frameworks, so it comes fully usable out-of-the-box.
-
Read the Quickstart and Usage Guide. You will need Xcode 10 (and iOS 12 if you wish to run on an iPhone or iPad.)
Skill Level: Intermediate: Although OK is not presented in a form suitable for absolute beginners, mostly because I'm too lazy to write documentation from step zero, it's not "advanced" level stuff either; if you've read the Swift Language Book and have attempted to make a SpriteKit game in Xcode, you are ready to use OK!
You should also read about the "Composition over inheritance" and "Entity–component–system" patterns if you're not already familiar with those concepts, although OK's implementation of these may be different than what you expect.
-
Stuck? See Tips & Troubleshooting.
-
Wondering whether something was intentionally done the way it is, or why? Coding Conventions & Design Decisions may have an explanation.
-
Want to keep tabs on what's coming or help out with the development of missing features? See the TODO & Roadmap.
-
This project may be referred to as "OctopusKit", "OK" or "OKIO" (for "OctopusKit by Invading Octopus") but "IOOK" sounds weird.
-
License: Apache 2.0
-
Tell me how awesome or terrible everything is: Discord, Twitter or 🆂hinryaku🆃ako@🅘nvading🅞ctopus.ⓘⓞ
-
Support my decadent lifestyle so I can focus on making unsaleable stuff: My Patreon
-
This project is not affiliated in any way with Apple.
OctopusKit © 2018 Invading Octopus • Apache License 2.0