UntoldEngine

An easy to use, Open-Source, 3D game engine for iOS/macOS game development.

343
32
Swift

Logo


Project license

Pull Requests welcome
code with love by untoldengine

Table of Contents

About

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


Getting Started

Prerequisites

To begin using the Untold Engine, you’ll need:

  • An Apple computer.
  • The latest version of Xcode, which you can download from the App Store.

Installation

Follow these steps to set up and run the Untold Engine:

  1. Clone the Repository
git clone https://github.com/untoldengine/UntoldEngine

cd UntoldEngine
  1. Open the Swift Package
open Package.swift
  1. Configure the Scheme in Xcode
  • In Xcode, select the “UntoldEngineTestApp” scheme.
  • Set “My Mac” as the target device.

xcodescheme

  1. Click on Run

You should see models being rendered.

gamesceneimage

Controls

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.

Edit Mode

In Edit Mode, you can navigate the scene and adjust the environment with ease using the following controls:

  • Orbit: Click and drag to rotate the view around the scene.
  • Move:
    • Use the W, A, S, and D keys to move forward, backward, left, and right.
    • Use the Q and E keys to move vertically (up and down).
  • Zoom: Pinch to zoom in or out for a closer or wider view.

Play Mode

In Play Mode, the scene comes to life! You will experience:

  • Animated characters performing actions.
  • Physics simulations running dynamically.

Toggle between Edit Mode and Play Mode with the P key to seamlessly explore or interact with the scene.


Core API Functions

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.

  1. createEntity()

This function generates a new entity.
Think of it as creating a “placeholder” object in your game world.

let myEntity = createEntity()
  1. setEntityMesh()

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")
  1. setEntityKinetics()

Enable physics for your entity, allowing it to move, fall, or be affected by forces.

setEntityKinetics(entityId: myEntity)
  1. setEntityAnimation()

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

An Example: Creating a Player Character

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

Creating a quick game

Step 1 Create a macOS game in Xcode

  • Open Xcode: File → New → Project.
  • Select Command Line Tool under macOS.
  • Click Next, name your game, and choose Swift as the language.

createterminalgame

Step 2: Add the Untold Engine as a Package Dependency

  • Go to: File → Add Packages…

  • Enter the repository URL:

https://github.com/untoldengine/UntoldEngine.git

  • Select the appropriate version or branch (e.g., main) and click Add Package.

addpackage


Step 3: Add boiler plate code to the AppDelegate

Main Setup

  1. Open main.swift and import the required modules:
import Cocoa
import MetalKit
import UntoldEngine

importmodules

  1. Modify the appDelegate to perform the following:

    • Create a window
    • Initialize the Untold Engine
    • Create a game scene

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

appdelegatecode

Add the GameScene Class

Add the GameScene class to main.swift:

class GameScene {

    init() {
        
    }
    
    func update(deltaTime: Float) {

    }

    func handleInput() {

    }

}

addgamescene

If everything was done correctly, you should see a window with a grid once you hit “Run”.

untoldenginegrid


Adding game entities

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)

    }

}

Adding a Sunlight Entity

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:

players

To enter Game Mode, press P.


Deep Dive into the engine

The following articles can help you get a deeper understanding on how to use the Untold Engine for your game.


Roadmap

See the open issues for a list of proposed features (and known issues).


Support

Reach out to the maintainer at one of the following places:


Project assistance

If you want to say thank you or/and support active development of Untold Engine:

  • Add a GitHub Star to the project.
  • Tweet about the Untold Engine.
  • Write interesting articles about the project on Dev.to, Medium or your personal blog.

Together, we can make Untold Engine better!


Contributing

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.


License

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.


Common Issues

ShaderType.h not found

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

bridgeheader

Linker issues

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.

linkerissue