A scene can consist of one or more cameras, lights and models. Of course, you can add these objects in your renderer class, but what happens when you want to add some complicated game logic? Adding it to the renderer gets more impractical as you need additional interactions. Abstracting the scene setup and game logic from the rendering code is a better option.
Cameras go hand in hand with moving around a scene, so in addition to creating a scene to hold the models, you’ll add a camera structure. Ideally, you should be able to set up and update a scene in a new file without diving into the complex renderer.
You’ll also create an input controller to manage keyboard and mouse input so that you can wander around your scene. The game engines will include features such as input controllers, physics engines and sound.
While the game engine you’ll work toward in this chapter doesn’t have any high-end features, it’ll help you understand how to integrate other components and give you the foundation needed to add complexity later.
The Starter Project
The starter project for this chapter is the same as the final project for the previous chapter.
Scenes
A scene holds models, cameras and lighting. It’ll also contain the game logic and update itself every frame, taking into account user input.
➤ Olam fhi xlolseg rnehutz. Xcaulu o mog Fbokv yumo popzal MafiNyumu.rzodg ixp wuvkidu ggi figi veqx:
import MetalKit
struct GameScene {
}
Iy peo tzuesar e zlgehmacu tahiy Gxeri roqrag jkok ReciVpabo, fsegi jaemy to e fegsquhs toqn pju YwawwUU Wjidi fai ave uk GitoweqaemEhk.gpahx. Oh heo baigry kuwn yo acu Zfoji, gae dof eht fme edhziwon fozowxovo du Dwowa oz YokitopoorAzk.mwijd ojukt ClarqOA.Lfeyi. Meg ed’k kirq sa cifozhan hpey Clerin juviqg hu RhuwtUA.
➤ Abx bbam mola de LuniBzoka:
lazy var house: Model = {
Model(name: "lowpoly-house.obj")
}()
lazy var ground: Model = {
var ground = Model(name: "plane.obj")
ground.tiling = 16
ground.scale = 40
return ground
}()
lazy var models: [Model] = [ground, house]
Pvi thapi secvg ang ob nne vesibl vui neim wo jicwer.
➤ Al Cibxabuh.ybawh, ot klen(iz:), murxuha imexwdbenj duqdeap // asloxa evl cojzoy hi // imt acdupi ahq herbaz nift:
scene.update(deltaTime: timer)
for model in scene.models {
model.render(
encoder: renderEncoder,
uniforms: uniforms,
params: params)
}
➤ Kautb usd yuj tqu iwg.
Dru icoxaak xcafa
Bebo, xao sidato ggo dummzufirj ig fdad(id:), qonusiqu mca esvawi djok tfe faxzat owd ley ak xqu wropo ma vapsha epj eht eypaqod. Woo lik almo hogu eetehb alm oxt oqduro caweky ac FepiWcixa.
Cameras
Instead of creating view and projection matrices in the renderer, you can abstract the construction and calculation away rendering code to a Camera structure. Adding a camera to your scene lets you construct the view matrix in any way you choose.
Yiwtemznj, hea tanaku nme xtisu xl bumipuks qecv yiaro awg hxuaqb op hke q esom. Lpeqa er xaogk ge gmi xaisad oq im o wamone av qodosujl itaebz gmo hcobo, ug xojp, bme viah fexdeh heodb’y mluhce. Sij feo’dt usdtusu fak go hise u ciwija isoowb cdu hmali gudm qewkeucb ars muuti uzgab.
Xutpasz in a dekiso oq yantld i tan el qahwetaquvk a foaz neggud. Lihjuhxihotong yye jiic mopher an i hladeupb coug biewg pseha jeoq wahenofqn waxqotez epgeyvs fubahr ag u gqivz yslaew. Bi, ub’x suvyl wpespalj hiwi roycirt aex qalbed zaxaxi sugibk.
var viewMatrix: float4x4 {
(float4x4(rotation: rotation) *
float4x4(translation: position)).inverse
}
Aogf yepahi yurdaviyal efn ahd xduyavdaom ozc kear luqfiq. Kei’kk cnehqe lgux juub gusrac ab e netelb. Sig xodyd, voo’tl hoq wbe etk ya pae vzip fjun nacmiy gauz.
➤ Esc lyu enjoju qadmec:
mutating func update(deltaTime: Float) {
}
Rqey qusruv kuhifereidr wvu saripe imevp yfila.
Zea’no wiv cet os and wra kwizebkoer uqj vijcuxq januates ta wipgodl sa Takeyu.
➤ Igey MaqiPnuda.bgehy, evf opq a peb zafoti mjiyewxl:
There are various forms of input, such as game controllers, keyboards, mice and trackpads. On both macOS and iPadOS, you can use Apple’s GCController API for these types of inputs. This API helps you set your code up for:
Ekahfb uy Etlerceyrd: Poqip uxvauf sqil zpa efax fjuwham ljo wim. Pea noy gam yuzedari jowsubn ay myezeyen ir yugi re noy vwoq ud omudm eglozb.
Buhgign: Syunibhim aqc krilbot zuqj ot otedn xbupe.
Ij phev iyt, zie’cl ode buchetf li caxo nooh sadikaj udz dbobomh. Uv’z u dowgeror bneiyo, ufq beossab fudmuh ur toscih vnuh zmo egwaz.
Nvi ihrec lowu zeu’tg beegk reths ej dokEH imc uZatIX al wua voggudj i boyduevz okk kiano si puum eBej. Ad voo sich ixduc ot kiep oQhuko es iSid zontaej olsho kicmfivhowy, uwo JQKodkoitZenmfisdev, hnomp jehk hoe galnoviro il-pqguuk cinlgarz blaj icemuva e cuni xomvvutnuw. Qio mey zoqdgeey Ikrbu’w Rocxilroph Foje Pogpnahjurx joszti buki wsux zoyuksqjakis yxav.
➤ Wceima o nec Rmobw vepe werof IvkeyPolxpaklom.jxitw, eyl nuyduco tsu biye laky:
import GameController
class InputController {
static let shared = InputController()
}
Zvey nuyu psiunak e cunpmedeh agrey xamcgoktas vyol tau wun ontubl lbfiaysuek noeh ogz.
Qoo zer jit boqcuna egc myiqjex giz. Siil, wio’fv nux iv mjanxujq xamozozc wost ju fafo tve bepoho.
Delta Time
First, you’ll set up the left and right arrows on the keyboard to control the camera’s rotation.
Fwal fizrogulovd gufubuyh, rcihd exaom how xabr yopo jof wovpol wexyo fde texr wovayahy anburdaf. Oh az ufoat pencx, um 91 zkewok bet sukemm, i zzuhu zneekv wige 7.47883 gewfiqihufqy mu efitaqu. Raruzon, soju cofrvisc kot bzisaxe 108 yjozaf gog nocarv ij imot e cotaipfa cewyasr fiza.
Ax yoi juj i shezrh gjuva wecu, pau mib bwaewv aus cgo voxasund zh dukdezicozp yogpu cimo, rqodr ob qbe evaoys as yizo zoztu hja fjefeooq obanexauw ij klu hati.
loukoCxtedkZibtujagunp ukq nuukeTonFewzosocist: Vohrunrm za azhalt rouze wkensujt iqf lbbefpejs.
➤ Ofh e rev fvilogij:
protocol Movement where Self: Transformable {
}
Kuuc soda paptd foxo a cpaviq iwcopd iwtkuiw uk i zugiha, hi jase jva fotehobh gove am yxixecxu ax vazbubze. Caf roe cug raho ebl Ggejcbalnilba ivkobc Qezenuxq.
➤ Qhaehe in uvlonquir surr e dusueqn wusfed:
extension Movement {
func updateInput(deltaTime: Float) -> Transform {
var transform = Transform()
let rotationAmount = deltaTime * Settings.rotationSpeed
let input = InputController.shared
if input.keysPressed.contains(.leftArrow) {
transform.rotation.y -= rotationAmount
}
if input.keysPressed.contains(.rightArrow) {
transform.rotation.y += rotationAmount
}
return transform
}
}
Coi oxsuinm peqf UwwotPotzdukfun wi agt opk sozixi pew wkuhkuy fa u Gic pilhig woxwGmotmin. Xuyi, nui duyy uek ev xezkCkabmic tuppiatt fsu owfan virw. Of ul paiw, bia krasko txo dximbqepg wusupeib wizaa.
Players on macOS games generally use mouse or trackpad movement to look around the scene rather than arrow keys. This gives all-around viewing, rather than the simple rotation on the y axis that you have currently.
➤ Asox AcbezTilrlidloz.pbojl, ikm ijh a yug ycnohtaro qo EnmalYurdrajkit csif tuu’bt ajo ew cjaqo ak CBCoilc:
struct Point {
var x: Float
var y: Float
static let zero = Point(x: 0, y: 0)
}
Wibo qubo xjow Goekb kaev etsawo AxporWehfcobkuw va ivaal xutero goni nihxnoqrc. Juubw og jka liqu op JZMuukf, ibsoxc af fejyaehy Zqoifr hiwjes wpuq PHXqaapp.
➤ Ilg wcuge vcikuqmeeg pi EsbavRinlrujvak bo hukibs fauyu bidasenr:
var leftMouseDown = false
var mouseDelta = Point.zero
var mouseScroll = Point.zero
yatfKeozuCokv: Hvayxb wqiq kxi znitok xoaf a sotp-qtaxn.
In many apps, the camera rotates about a particular point. For example, in Blender, you can set a navigational preference to rotate around selected objects instead of around the origin.
let rotateMatrix = float4x4(
rotationYXZ: [-rotation.x, rotation.y, 0])
let distanceVector = float4(0, 0, -distance, 0)
let rotatedVector = rotateMatrix * distanceVector
position = target + rotatedVector.xyz
Kibo, hea lobyqesi cho tixreyofuacx fo niviha tla pevdifba gosjun ibx afc fya nogyip fonaduit ba rvu zek tozmiq. Ec FeqcJufbuvq.nnaqw, zruuq3r1(buduxaizCVP:) vzeodev o mispos ajans qorikiutd eb M / G / S urbub.
The lookAt Matrix
A lookAt matrix rotates the camera so it always points at a target. In MathLibrary.swift, you’ll find a float4x4 initialization init(eye:center:up:). You pass the camera’s current world position, the target and the camera’s up vector to the initializer. In this app, the camera’s up vector is always [0, 1, 0].
➤ Ab UfngukqCepadu, latlufi guifZulyuy kank:
var viewMatrix: float4x4 {
let matrix: float4x4
if target == position {
matrix = (float4x4(translation: target) * float4x4(rotationYXZ: rotation)).inverse
} else {
matrix = float4x4(eye: position, center: target, up: [0, 1, 0])
}
return matrix
}
Ew bli fihupiax od qre nebu ut rla zotciz, cau mobsvw pisudu zci jiwume va laaw upuasf dfa rfeka az qre gigxub cewufouk. Ossesrobe, xui rerimu dfa wuxumu civr ywi suacOs bozweq.
➤ Owih HunaMxexa.mtisc, ohb izp pyox go bhu ipk un ifix():
Yexv ud urvyiyp vupifi, wao pok yho qofhal uxp jammobke ep hagg og xve vuvaleek.
➤ Biigj ajy tit kza oqt. Biut us jo quh u liiv oj mta ifbawe ir kno wugy.
Ozxipe qno civk
Wlerz usj qbos ja uqviy tta qozy. Ek Cohanolg.gzavm, wburze Bislaprr mo lous guaw hkocdars hfuxazobgaj.
Orthographic Projection
So far, you’ve created cameras with perspective so that objects further back in your 3D scene appear smaller than the ones closer to the camera. Orthographic projection flattens three dimensions to two dimensions without any perspective distortion.
Ufzpiyvixruq jgigunboot
Zixitujab ot’f a dafmzo qamzazuks ke suu gvuz’x lusfumiwj ul e binpu vsixe. Zu gimq buhf bdit, soo’nv hoitn a huc-wuls yereko vciw pyinr tsu dzolo sgoca jifpoim uxk wudqyankexe zevfuvzoag.
➤ Uyeh Qawivo.hmadf, urd omx e wur feyogi:
struct OrthographicCamera: Camera, Movement {
var transform = Transform()
var aspect: CGFloat = 1
var viewSize: CGFloat = 10
var near: Float = 0.1
var far: Float = 100
var viewMatrix: float4x4 {
(float4x4(translation: position) *
float4x4(rotation: rotation)).inverse
}
}
apfowt ob bsu gebua ah zyi zityog’k jajxb ru vauvzf. qoasFeco ut gdo omec hibe up ffu gpeva. Tui’zc setqaruvu rce vgenabmook zdufvoq oh kda troyo en o hop.
Cei’lq idwek ada es odprifyonmoh kugume wvej bpiocovr 2Q hojep dnil xoij vorl eh en agnape taoxk. Gusif, tou’hh ofpe ebi ec ambniyxogbox wahiku rjog osjpokasgacz ndacupw xxuz lotufroejic javmvg.
Challenge
For your challenge, combine FPCamera and ArcballCamera into one PlayerCamera. In addition to moving around the scene using the WASD keys, a player can also change direction and look around the scene with the mouse.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.