-
Notifications
You must be signed in to change notification settings - Fork 1
Home
A Swift framework that brings Unity3D's familiar API and coding patterns to iOS development. Built on top of SceneKit, UnityKit allows developers experienced with Unity to write iOS games using the same concepts, components, and lifecycle methods they already know.
- Familiar Unity API: Use GameObject, MonoBehaviour, Transform, Rigidbody, and other Unity concepts in Swift
- Follow C# Tutorials: Take Unity tutorials written in C# and implement them in Swift with minimal translation
- SceneKit Foundation: Built on Apple's SceneKit for high-performance 3D rendering
- Multiple Scenes: Run multiple scenes simultaneously - something Unity can't do on iOS
- Production Ready: Comprehensive test suite with 399 tests covering critical functionality
- Modern Swift: Written in Swift 5.9+ with a clean, type-safe API
- iOS 13.0+
- Xcode 14.0+
- Swift 5.9+
Add UnityKit to your project via Xcode:
- In Xcode, go to File > Add Package Dependencies
- Enter the repository URL:
https://github.com/salt-pepper-code/UnityKit.git - Select the version or branch you want to use
- Click Add Package
Or add it to your Package.swift:
dependencies: [
.package(url: "https://github.com/salt-pepper-code/UnityKit.git", from: "1.1.2")
]Then import wherever needed:
import UnityKitAdd UnityKit to your Cartfile:
github "salt-pepper-code/unitykit" "master"
Run:
carthage updateIn your application target's General tab, drag UnityKit.framework from Carthage/Build/iOS to Linked Frameworks and Libraries.
import UnityKit
class GameViewController: UIViewController {
override func loadView() {
// Simple setup
self.view = UI.View.makeView()
// Or with a custom scene file and options
// self.view = UI.View.makeView(
// sceneName: "Scene.scn",
// options: UI.View.Options(showsStatistics: true)
// )
}
var sceneView: UI.View {
return self.view as? UI.View ?? UI.View(frame: .zero)
}
}// Scene uses singleton pattern by default
guard let scene = Scene.shared else { return }
// Or access via the view
guard let scene = sceneView.sceneHolder else { return }// Empty GameObject
let gameManager = GameObject(name: "GameManager")
scene.addGameObject(gameManager)
// GameObject with primitive shape
let cube = GameObject.createPrimitive(
.cube(width: 20, height: 20, length: 20, chamferRadius: 0, name: "MyCube")
)
scene.addGameObject(cube)Available Primitives:
.sphere(radius: Float, name: String?)
.capsule(capRadius: Float, height: Float, name: String?)
.cylinder(radius: Float, height: Float, name: String?)
.cube(width: Float, height: Float, length: Float, chamferRadius: Float, name: String?)
.plane(width: Float, height: Float, name: String?)
.floor(width: Float, length: Float, name: String?)Just like Unity, create custom behavior by subclassing MonoBehaviour:
class PlayerController: MonoBehaviour {
var speed: Float = 10.0
override func start() {
Debug.info("Player controller started!")
}
override func update() {
// Handle movement
if Input.getKey(.w) {
transform.position += transform.forward * speed * Time.deltaTime
}
}
override func onCollisionEnter(_ collision: Collision) {
Debug.info("Collided with \(collision.gameObject.name)")
}
}Add components to GameObjects:
let player = GameObject(name: "Player")
player.addComponent(PlayerController.self)
scene.addGameObject(player)UnityKit implements the familiar Unity lifecycle methods:
class MyBehaviour: MonoBehaviour {
/// Called when the script instance is being loaded
override func awake() { }
/// Called before the first frame update
override func start() { }
/// Called every frame before rendering
override func preUpdate() { }
/// Called every frame after rendering
override func update() { }
/// Called every fixed timestep (for physics)
override func fixedUpdate() { }
/// Called after all Update functions
override func lateUpdate() { }
}/// Called when the component is enabled
override func onEnable() { }
/// Called when the component is disabled
override func onDisable() { }
/// Called when this collider/rigidbody begins touching another
override func onCollisionEnter(_ collision: Collision) { }
/// Called when this collider/rigidbody stops touching another
override func onCollisionExit(_ collision: Collision) { }
/// Called when a collider enters a trigger collider
override func onTriggerEnter(_ collider: Collider) { }
/// Called when a collider exits a trigger collider
override func onTriggerExit(_ collider: Collider) { }let obj = GameObject(name: "MyObject")
// Position, rotation, scale
obj.transform.position = Vector3(0, 10, 0)
obj.transform.rotation = Quaternion.euler(0, 45, 0)
obj.transform.localScale = Vector3(2, 2, 2)
// Direction vectors
let forward = obj.transform.forward
let right = obj.transform.right
let up = obj.transform.up
// Look at target
obj.transform.lookAt(target: targetObject.transform)
// Hierarchy
parent.addChild(child)
child.parent = parent// Add Rigidbody for physics
let rb = gameObject.addComponent(Rigidbody.self)
rb?.mass = 10.0
rb?.useGravity = true
// Add Colliders
let boxCollider = gameObject.addComponent(BoxCollider.self)
boxCollider?.set(size: Vector3(1, 1, 1))
let sphereCollider = gameObject.addComponent(SphereCollider.self)
sphereCollider?.set(radius: 0.5)
// Raycasting
if let hit = Physics.Raycast(
origin: transform.position,
direction: transform.forward,
maxDistance: 100,
layerMask: .all
) {
Debug.info("Hit \(hit.gameObject.name) at distance \(hit.distance)")
}
// Overlap queries
let nearbyObjects = Physics.overlapSphere(
center: transform.position,
radius: 10.0,
layerMask: .all
)class InputController: MonoBehaviour {
override func update() {
// Keyboard
if Input.getKey(.space) {
// Space is being held
}
if Input.getKeyDown(.w) {
// W was just pressed this frame
}
if Input.getKeyUp(.s) {
// S was just released this frame
}
// Mouse
if Input.getMouseButton(0) {
// Left mouse button held
}
let mousePosition = Input.mousePosition
// Check for any key
if Input.anyKey {
Debug.info("Any key is pressed")
}
}
}class MyBehaviour: MonoBehaviour {
override func start() {
// Start coroutine on background thread
startCoroutine({
// Heavy computation here
}, thread: .background)
// Queue coroutine with exit condition
queueCoroutine((
execute: {
Debug.info("Executing...")
},
exitCondition: { timePassed in
return timePassed >= 2.0 // Exit after 2 seconds
}
), thread: .main)
}
}if let clip = AudioClip(fileName: "BackgroundMusic.wav", playType: .loop) {
let audioSource = gameObject.addComponent(AudioSource.self)
audioSource?.configure {
$0.clip = clip
$0.volume = 0.3
$0.play()
}
}let lightObj = GameObject(name: "MainLight")
let light = lightObj.addComponent(Light.self)
light?.configure {
$0.type = .directional
$0.intensity = 2000
$0.castsShadow = true
$0.shadowRadius = 5.0
}
scene.addGameObject(lightObj)let particles = gameObject.addComponent(ParticleSystem.self)
particles?.load(fileName: "Fire.scnp", loops: true)
.execute { system in
// Customize particle system
}// Find by name
let player = GameObject.find(.name(.exact("Player")), in: scene)
// Find by tag
let enemies = GameObject.findGameObjects(.tag(.custom("Enemy")), in: scene)
// Find by layer
let groundObjects = GameObject.findGameObjects(.layer(.ground), in: scene)
// Find with camera
let mainCamera = GameObject.find(.camera, in: scene)
// Find with light
let lights = GameObject.findGameObjects(.light, in: scene)
// Combined search
let results = GameObject.findGameObjects(.name(.contains("Enemy")), .layer(.default), in: scene)class GameController: MonoBehaviour {
override func update() {
// Delta time since last frame
let delta = Time.deltaTime
// Time since game started
let elapsed = Time.time
// Control time scale (1.0 = normal, 0.5 = half speed, 2.0 = double speed)
Time.timeScale = 0.5
}
}UnityKit includes a comprehensive logging system:
// Enable all logs
Debug.set(enable: .all)
// Or be selective
Debug.set(enable: .warning)
Debug.set(enable: .error)
// Log levels
Debug.debug("Debug message")
Debug.info("Info message")
Debug.warning("Warning message")
Debug.error("Error message")Available log levels:
-
.all- Show everything -
.debug- Debug messages only -
.info- Info and above -
.warning- Warnings and errors -
.error- Errors only -
.none- Silence all logs
UnityKit includes these built-in components:
- Transform - Position, rotation, scale, and hierarchy
- Camera - Scene rendering and view controls
- Light - Lighting system (omni, directional, spot, ambient)
- MeshFilter - Geometry data
- MeshRenderer - Rendering properties
- Rigidbody - Physics simulation
- BoxCollider - Box-shaped collision detection
- SphereCollider - Sphere-shaped collision detection
- CapsuleCollider - Capsule-shaped collision detection
- MeshCollider - Mesh-based collision detection
- PlaneCollider - Infinite plane collision detection
- AudioSource - 3D audio playback
- AudioClip - Audio file management
- ParticleSystem - Particle effects
- Vehicle - Vehicle physics (wheels, steering, engine)
- Canvas - UI container
- Image - Image rendering
- Slider - Interactive slider control
UnityKit has a comprehensive test suite to ensure reliability and stability:
- 399 Tests across 20 test suites
- Critical Framework Tests: Transform, Scene, GameObject hierarchy, Colliders, Coroutines
- Component Tests: Light, ParticleSystem, Rigidbody, Camera
- System Tests: Physics, Input, Time, Lifecycle
- Utility Tests: Vector2, Vector3, Quaternion, Volume calculations
Tests require an iOS Simulator. Use Xcode or command line:
# Run all tests
xcodebuild test -scheme UnityKit -destination 'platform=iOS Simulator,name=iPhone 17 Pro'
# Run specific test suite
xcodebuild test -scheme UnityKit -destination 'platform=iOS Simulator,name=iPhone 17 Pro' -only-testing:UnityKitTests/TransformTests
A complete implementation of Unity's famous Tanks Tutorial, recreated in Swift using UnityKit. Originally recorded at Unite Boston 2015, this project demonstrates:
- Player and AI tank controllers
- Physics-based projectile system
- Health and damage system
- World and screen-space UI
- Audio mixing and effects
- Game architecture patterns
Original Unity Tutorial (C#) →
This demo proves you can follow Unity C# tutorials and implement them in Swift with UnityKit!
While GameObject wraps SCNNode, it's recommended to work with the UnityKit API rather than accessing the underlying SCNNode directly. Use components like MeshFilter to access geometry data.
Scenes can be allocated as singletons or instances:
// Singleton (default) - accessible via Scene.shared
let scene = Scene(allocation: .singleton)
// Instance - for multiple scenes
let scene = Scene(allocation: .instantiate)Organize GameObjects using layers and tags:
// Layers (OptionSet - can combine)
gameObject.layer = .player
gameObject.layer = [.player, .default]
// Available layers: .default, .ground, .player, .environment, .projectile, .all
// Tags (Enum - single value)
gameObject.tag = .mainCamera
gameObject.tag = .custom("Enemy")
gameObject.tag = .untagged- Use Components: Separate concerns into reusable MonoBehaviour components
-
Lifecycle Methods: Always call
superwhen overriding lifecycle methods -
Component Initialization: Call
awake()on components in tests - Physics Queries: Use layer masks to filter physics queries for better performance
- Coroutines: Use background threads for heavy computation, main thread for UI updates
-
Memory Management: Remove GameObjects with
destroy()when no longer needed
If you're coming from Unity, here are key differences:
| Unity (C#) | UnityKit (Swift) |
|---|---|
void Start() |
override func start() |
gameObject.GetComponent<T>() |
gameObject.getComponent(T.self) |
gameObject.AddComponent<T>() |
gameObject.addComponent(T.self) |
Instantiate(prefab) |
GameObject.instantiate(original) |
Destroy(gameObject) |
gameObject.destroy() |
Vector3.zero |
Vector3.zero (same!) |
Time.deltaTime |
Time.deltaTime (same!) |
Most Unity concepts translate directly to UnityKit!
- GitHub Wiki - Complete API documentation, examples, and guides
- API Documentation - Use Xcode's Quick Help (⌥ + Click) for inline documentation
Contributions are welcome! Please feel free to submit pull requests or open issues for bugs and feature requests.
Kevin Malkic - Salt and Pepper Code Ltd
UnityKit is released under the MIT license. See LICENSE for details.
Made with ❤️ for Unity developers wanting to build native iOS games in Swift