This page goes through how to build the 3D game using both Mac OS/X for the Apple Mac Desktop and iOS (iPhone, iPad)
Create a new project in Xcode. Select the iOS Game first.
Type the name of the game. I logged into my Apple account under Team. Select Objective-C and Metal.
Select the location for your project
Click on the Blue Blindfate iOS and add a new Target by clicking on the plus sign underneath “TARGETS”.
Select the macOS Game this time:
Pick a name – this time with MacOS
Select the MacOS version, and press the Play button
You will see a rotating coloured cube as below
Now select the iOS app and your device of your choice via a Simulator. You can also build it directly to your phone or even iPad (you will need to give your app permission to run under General -> Settings -> Device Management -> Apple Development)
You will see the 3D rotating cube on the iOS device:
Apple iOS & MacOS Code Base
The documentation of Metal is here. This is a game template that that provides a starting point for our game. We will then modify it.
Note that the template has code for a 3D spinning cube.
My intention is to look at the sample code for metal. In particular, I will make use of the sample code project from “Modern Rendering with Metal“. This sample is written using Objective C.
A screenshot from the sample code is below. You can move around with the W,S,A,D keys and rotate around with your mouse.
Some metal tutorials are here as well as tutorial videos on Metal with iOS.
Setting up the App
Documentation on setting up an app for iOS and MacOS is via the apple developer documentation. To develop for iOS, you use UIKit and for MacOS, you use AppKit. This documentation talks about Swift, but you can translate it to Objective C. Information on using Objective C is here plus a good Objective C Tutorial is here.
Objects in Objective C are derived from NSObject (or other built it objects) and then classes can be created using @interface, then you implement methods using @implementation and access public values using @property – hence this is a little bit different in syntax to using the C++ class{}, although I have seen @class used as well (see the sample above). But Objective C does work pretty similar to C++.
If you look at the code, the iOS app is separate from the MacOS app. Also some objects for the iOS app are named differently to the MacOS app.
The file main.m (desktop) and main.m (iOS) runs the main window for the app. It returns UIApplicationMain for iOS and NSApplicationMain for MacOS.
For iOS, UIApplication is the iOS app and for MacOS, NSApplication is the MacOS app. Built into the application is an event handler which runs in a loop – this will be also our game loop.
The files AppDelegate.h and AppDelegate.m (desktop) and AppDelegate.h and AppDelegate.m (iOS) contains the code to handle events for the open window of the app whether it’s on the computer monitor or phone. For example, for iOS, you can check to see if the app entered the foreground or background. For MacOS, you can check if the user terminated or quit the app.
The files GameViewController.h and GameViewController.m (desktop) and GameViewController.h and GameViewController.m (iOS) contains the code that controls what you see on the app itself (the screen part of the app). What you see is called the view – iOS has UIViewController and MacOS has NSViewController, and the object GameViewController is created based on these. The view also calls the renderer to render objects on the screen (the 3D cube).
Setting up Metal and Rendering Graphics
Metal seems to have a similar structure and attributes as DirectX12. The initialisation of Metal and rendering are done in an object created called Renderer.
The files Renderer.h and Renderer.m (desktop) and Renderer.h and Renderer.m (iOS) contain the code to render the graphics to the app window or screen and the files ShaderTypes.h and Shaders.metal contain the shaders.
In Renderer.h, the object or class Renderer calls the initWithMetalKitView method which points to the view (or app window). Behind the scenes with metal kit, there is a game timer and allows updates to be made per frame.
The implementation of the Renderer object is in Renderer.m
The steps to setup metal and render graphics is initialising some variables to use for setting up metal and then using initWithMetalKitView to
- create the view device
- create a semaphore (thread) to initialise buffers (memory)
- load metal with the view via the method _loadMetalWithView
- load the graphics assets
The method _loadMetalWithView does the following:
- setup the view with defining the pixel formats (colour and depth stencil)
- setup vertex descriptors
- setup the graphics pipeline states and create the pipeline
- setup the Depth Stencil
- create the back buffer
- create a command queue
The method _loadAssets does the following:
- create a mesh buffer
- create the mesh – in this case, it’s a 3D box using the function newBoxWithDimensions()
- load vertex descriptors
- create the metal kit mesh
- create and load textures using a specific colour map
Another method is drawInMTKView which renders or draws the graphics to the view (app screen) once per frame. The steps involved are:
- setup the command queue
- update the back buffer
- update the game state (rotate the cube)
- get the render pass descriptor to prep for rendering
- send rendering commands to the command queue
- set the cull mode
- set the graphics pipeline state
- set the depth stencil state
- set the vertex buffer
- set the fragment buffer
- set the fragment texture
- cycle through meshes (if more than one) and draw them
- present the graphics to the view (app screen)
The method _updateGameState is called by drawInMTKView once per frame. It is involved in rotating the cube and setting up the view to see the graphics on the screen as the cube rotates.
The method drawableSizeWillChange will respond if the view size changes (user resizes the app window). This method also sets up the _projectionMatrix which sets up the camera. _projectionMatrix is also in the _updateGameState method.
Where to put Game specific code
We can alter the existing files as needed and add new files as required.