An easy to use, Open-Source, 3D game engine for iOS/macOS game development.
The Untold Engine is a 3D game engine for macOS/iOS devices, written in Swift and powered by Metal as its graphics library. Its primary goal is to simplify game development by providing an intuitive, easy-to-use API.
Author: Harold Serrano
To begin using the Untold Engine, you’ll need:
Follow these steps to set up and run the Untold Engine:
git clone https://github.com/untoldengine/UntoldEngine
cd UntoldEngine
open Package.swift
You should see models being rendered.
The Untold Engine provides two distinct modes for interaction: Edit Mode and Play Mode. You can switch between these modes at any time by pressing the P key.
In Edit Mode, you can navigate the scene and adjust the environment with ease using the following controls:
In Play Mode, the scene comes to life! You will experience:
Toggle between Edit Mode and Play Mode with the P key to seamlessly explore or interact with the scene.
The Untold Engine API is designed to make game development straightforward and efficient. Its key strength lies in its clear and consistent naming conventions, enabling developers to focus on building their game logic without worrying about complex engine details.
At its core, the API revolves around the Entity-Component-System (ECS) architecture, where you create entities and enhance them with components like meshes, physics, collisions, and animations. Let’s break down the most commonly used API functions.
This function generates a new entity.
Think of it as creating a “placeholder” object in your game world.
let myEntity = createEntity()
Attach a visual representation (a model) to your entity.
This is where your 3D model comes to life.
setEntityMesh(entityId: myEntity, filename: "myModel", withExtension: "usdc")
Enable physics for your entity, allowing it to move, fall, or be affected by forces.
setEntityKinetics(entityId: myEntity)
Add animations to your entity.
You provide an animation file and name it for easy reference.
setEntityAnimations(entityId: myEntity, filename: "walkAnimation", withExtension: "usdc", name: "walking")
Here’s how the API comes together to build a fully interactive player character:
// Step 1: Create the entity
let player = createEntity()
// Step 2: Attach a mesh to represent the player visually
setEntityMesh(entityId: player, filename: "playerModel", withExtension: "usdc")
// Step 3: Enable physics for movement and gravity
setEntityKinetics(entityId: player)
// Step 4: Add collision detection for interacting with the world
setEntityCollision(entityId: player)
// Step 5: Add animations for walking and running
setEntityAnimations(entityId: player, filename: "walkingAnimation", withExtension: "usdc", name: "walking")
setEntityAnimations(entityId: player, filename: "runningAnimation", withExtension: "usdc", name: "running")
// Step 6: Play an animation
changeAnimation(entityId: player, name: "walking")
Go to: File → Add Packages…
Enter the repository URL:
https://github.com/untoldengine/UntoldEngine.git
import Cocoa
import MetalKit
import UntoldEngine
Modify the appDelegate to perform the following:
This process is shown below. Copy the code below:
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var renderer: UntoldRenderer!
var gameScene: GameScene!
func applicationDidFinishLaunching(_: Notification) {
print("Launching Untold Engine v0.2")
// Step 1. Create and configure the window
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 1280, height: 720),
styleMask: [.titled, .closable, .resizable],
backing: .buffered,
defer: false
)
window.title = "Untold Engine v0.2"
window.center()
// Step 2. Initialize the renderer and connect metal content
guard let renderer = UntoldRenderer.create() else {
print("Failed to initialize the renderer.")
return
}
window.contentView = renderer.metalView
self.renderer = renderer
renderer.initResources()
// Step 3. Create the game scene and connect callbacks
gameScene = GameScene()
renderer.setupCallbacks(
gameUpdate: { [weak self] deltaTime in self?.gameScene.update(deltaTime: deltaTime) },
handleInput: { [weak self] in self?.gameScene.handleInput() }
)
window.makeKeyAndOrderFront(nil)
NSApp.setActivationPolicy(.regular)
NSApp.activate(ignoringOtherApps: true)
}
func applicationShouldTerminateAfterLastWindowClosed(_: NSApplication) -> Bool {
return true
}
}
// Entry point
let app = NSApplication.shared
let delegate = AppDelegate()
app.delegate = delegate
app.run()
Add the GameScene class to main.swift:
class GameScene {
init() {
}
func update(deltaTime: Float) {
}
func handleInput() {
}
}
If everything was done correctly, you should see a window with a grid once you hit “Run”.
Here’s how to load assets, create entities, and link models:
class GameScene{
init(){
// set camera to look at point
camera.lookAt(
eye: simd_float3(0.0, 7.0, 15.0), target: simd_float3(0.0, 0.0, 0.0),
up: simd_float3(0.0, 1.0, 0.0)
)
// You can load the assets in bulk as shown here.
// In this instance, stadium contains multiple assets which do not require an entity id to be assigned.
loadBulkScene(filename: "stadium", withExtension: "usdc")
// create an entity id for the blue player
let bluePlayer = createEntity()
// this function loads the usdc file and sets the mesh model to the entity
setEntityMesh(entityId: bluePlayer, filename: "blueplayer", withExtension: "usdc")
// translate the entity
translateEntityBy(entityId: bluePlayer, position: simd_float3(3.0, 0.0, 0.0))
// let's create another entity Id
let redPlayer = createEntity()
// load the usdc file and link the model to the entity
setEntityMesh(entityId: redPlayer, filename: "redplayer", withExtension: "usdc", flip: false)
// load and link the animation to the entity. You should give a name to the animation
setEntityAnimations(entityId: redPlayer, filename: "running", withExtension: "usdc", name: "running")
// set the animation to play. You reference the animaitons by name
changeAnimation(entityId: redPlayer, name: "running")
// enable physics/kinetics on the entity
setEntityKinetics(entityId: redPlayer)
}
}
And finally, let’s add a Sun light entity.
class GameScene{
init(){
// ... other initializations ...
// Create an entity for the directional light
let sunEntity: EntityID = createEntity()
// Create the directional light instance
let sun: DirectionalLight = DirectionalLight()
// Add the entity and the light to the lighting system
lightingSystem.addDirectionalLight(entityID: sunEntity, light: sun)
}
}
Click on Run and you should see the following:
To enter Game Mode, press P.
The following articles can help you get a deeper understanding on how to use the Untold Engine for your game.
See the open issues for a list of proposed features (and known issues).
Reach out to the maintainer at one of the following places:
If you want to say thank you or/and support active development of Untold Engine:
Together, we can make Untold Engine better!
Since this project has barely been released as an open-source, I am not taking Pull-Request yet. I want to complete the documentation and write more tutorials before allowing Pull-Request.
If you want to help out, I would appreciate if you could report back any bugs you encounter. Make sure to report them at our Github issues, so we all have access to them.
Thank you.
Once I feel that the documentation is ready, I will allow Pull-Request.
This project is licensed under the LGPL v2.1.
This means that if you develop a game using the Untold Engine, you do not need to open source your game. However, if you create a derivative of the Untold Engine, then you must apply the rules stated in the LGPL v2.1. That is, you must open source the derivative work.
Xcode may fail stating that it can’t find a ShaderType.h file. If that is the case, simply go to your build settings, search for “bridging”. Head over to ‘Objective-C Bridging Header’ and make sure to remove the path as shown in the image below
Xcode may fail stating linker issues. If that is so, make sure to add the “Untold Engine” framework to Link Binary With Libraries under the Build Phases section.