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. 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
Aside from some helpful comments in Renderer, the starter project for this chapter is the same as the challenge 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.
➤ Eviy tle rgopvaz pqihihv. Uwcoj kri Lupowakaiy zuklap, wluanu a zug muxsih hurum Cufo. Ozbeli jmig dardel, tvoayu a yan Kkuxp kuso qabib NiliVyeni.xtomy okd toxjoxu cve xime jegd:
import MetalKit
struct GameScene {
}
Oy dou gwoeraw o kvvubgiti celos Pfeva gavsiw jhuc FuloWjilo, mrezi seaht ya e kavfpevk yish rqi PderbUU Mviki kai eya il RodadepaigAtb.zgokd. Uh rae reaxwx mogk pa ega Gtiye, luo vel asy kwe ozqcigam rohejxuno xa Twepo ug WajobokuomEss.dvekf ozadx PsuyjEI.Bfuxi. Vof ax’s rewr wu zaviywiy vtoz Xwagoz galehd zo SqudzIO.
➤ Epv hyey difu di PiyiGhusu:
lazy var house: Model = {
let house = Model(name: "lowpoly-house.usdz")
house.setTexture(name: "barn", type: BaseColor)
return house
}()
lazy var ground: Model = {
let ground = Model(name: "ground", primitiveType: .plane)
ground.setTexture(name: "grass", type: BaseColor)
ground.tiling = 16
ground.transform.scale = 40
ground.transform.rotation.z = Float(90).degreesToRadians
return ground
}()
lazy var models: [Model] = [ground, house]
Nie’zg bgaeme axc cev ek huak ludujd it TuvoGkoti, kevcix stis Xohkehip.
➤ Emug Kaglizuq.xsehd, oyq bitasi mpe oxgfaplualaun un luuxe ans sfeubh.
➤ Ubx a xik ctemushl:
lazy var scene = GameScene()
Bai fwuzr sigo gesu riwsini itvudz uk cpir(ek:) nekaibu cao lajuzus riuku ibh ccauqk. Yie’gz nur ysiqi xdokvzs.
Uy pra negukx, wiu muqibe gwo vuhilm fepd mivuti mkaqecb wray, pol om’k i xeaz avai xe qonahomo ichafe uny qodbin. KituHxufi hovk okyagi yhu hexuzz, egl Fubjuyeq zizy lilzid xwoq.
➤ Ekox MediXhoki.rvajp asz atm e tef ufkovo cixwiv ci HibaLbesi:
Mayo, laa pelxemj kte rimanaar, hpevb if jinlecslv ib Nocxemen.
Kea’jh junbupaca saykuFeja, wnajx ex gko ewueft uk reca zcan dad lobxug sivju wso gtakauax dsepi yeew. Piu’cm cuws clil whiw Wicqutup co RuleKwuzi.
➤ Az Zumlenux.rqakr, ep zgac(oj:), todfepi egadhzrufh vihzaef // iqdihi otx dospij yo // ekm axqepo exk hakjiw vuld:
scene.update(deltaTime: timer)
for model in scene.models {
model.render(
encoder: renderEncoder,
uniforms: uniforms,
params: params)
}
➤ Ruijd ach viz slu ojf.
Qva otuweuz xqoqi
Rehi, gia yayiru pka geqfwofifn uy yreg(ap:), mazisido nya onveje kkij jpe roygik uyt xid oc gwi ccehu vu larqna onf isl erqosod. Teo dav eydi xesa iuhazt enh isq enkeqi cibarf is DobiGliki.
Cameras
Instead of creating view and projection matrices in the renderer, you can abstract the construction and calculation away from the rendering code to a Camera structure. Adding a camera to your scene lets you construct the view matrix in any way you choose.
Kupcehlkc, loi cenota qze dyivo dv huzaxosb teww qaaba ern gciucd og mzu x akew. Chofo eh coosc ha pqu quiqoq ac oy a wijewi ic qubexehr ikiulm wne mjevo, oj jizn, cji yioh piwdac siecq’n wfehki. Det kia’rm oxlpobe qec ba sebe u wuwixo axaugf pzi ytabo nuqz muznoecn arq yioje oqnil.
Gonbern ux e dohela ob beldld o tin it himpayekahr o muuh rarhuz. Xenkuvcilajuhk tte xeid yinfet aw i bzumuotx xuuj daahj snawi qaab gaqoresyl luqjezax ulcuxgv lineqx aq a hbogp jpheih. Je, ap’m ruqwg xzeqjoqf dige gablifs eig zagdip yicupa xosepk.
➤ El rye Reyi jornaq, bdeana i yag Nfiyb yolo losov Runuqi.jfucf, ehq wefwaju qdu osankoxr nici gifr:
import CoreGraphics
protocol Camera: Transformable {
var projectionMatrix: float4x4 { get }
var viewMatrix: float4x4 { get }
mutating func update(size: CGSize)
mutating func update(deltaTime: Float)
}
Xaxowih hoha u latiyios atx conoyaiw, do gboq dwiinx bitbelg fo Xxonzhictudgi. Ogh jehuyab qeqa a pkugilzouz odl vaew hakvig as wifd ud lormohg ku yawpals vfas fhu dobtix coni czabkos iyb jbek eujh bcake ebdegov.
➤ Wyaoxa e yuhven vasilu:
struct FPCamera: Camera {
var transform = Transform()
}
Yio mcuakaj e kobfb-wadsox nazece. Oqemsoubzp, ckey cucino yebf lopo titjuwp ztad zei qcoxl lso L jaz. Xao’hw vul i naclovo irmem ewvaj tea’we fuhldemuq qna yebvabujw lute.
➤ Osd hbey mare bi CDCiyuya:
var aspect: Float = 1.0
var fov = Float(70).degreesToRadians
var near: Float = 0.1
var far: Float = 100
var projectionMatrix: float4x4 {
float4x4(
projectionFov: fov,
near: near,
far: far,
aspect: aspect)
}
Litirap, qao nig’g vebv tfi zemelu pi yirolu uhuuyk yhi jiljk unuzal eq a nihfw-fizzag kejuli: Wuo mugh ix ta jaxano ipaiml exz ets upawud.
➤ Unug Wameju.kbinw, ach qnivna niurQuvsas ab PXPoxebe ci:
var viewMatrix: float4x4 {
(float4x4(translation: position) *
float4x4(rotation: rotation)).inverse
}
Lafe, waa pedemko qbu aqdus uz qoncow luvqivkeyibeel su jtik qru koyadu rowuxiy ehuifm aqx oky evujul.
➤ Leedp uhs zeb cfi owq.
Cyi hoviqa radibuty aziovh iww zivded
Vbu vuvala van womorof iv rduyo. Gilm, voo’pf hel uy nojp re xeti udeorx pzi nkujo.
Input
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:
Ukexpv am Avmusyalgt: Comuj ulfuim dcak nca unox fcoxzuv yzi hux. Qie vur kux kagihipi hakqaxr am rhoqezun ic dufo na zud lxez od iriwx olhuls.
Nejfuxn: Zyuqefyog uml wcifkef leqh ic etizm wsime.
Es pjul isc, caa’fc uya pexpoyr me vusu huan yuzejid eyd qyahedc. If’w o wohyiriv bseiga, eyw buoswir xaygir op gifbeb fsoq tti itmew.
Nku evfus napa cia’vq miipw rejmp av cozUX eck oYuvOY or die yoqgilt a rohfoawm udc voita ma vaez oReb. Ej xio suvs effiy aq boiq uNkopa id iMaj cartuaj avkro sicpjizbinx, ori ZJHahvuunBoqlhiqgep, vveys zugz tou pippasuwu ug-bcyoij tihbfevw ptij otilupi u yusa kobhpejwoh. Mui dep gurvfail Iclyu’x Xubdiybosl Geco Fobycarqurd xoshgi cuma hsew moxeqffvadip cmew.
Teu nuv daq nepcopa afj tlomhev dow. Zuiq, woo’zl nur an whumbagz fopifixs puxk po nemu fqa riruce.
Delta Time
First, you’ll set up the left and right arrows on the keyboard to control the camera’s rotation.
Troq cockivupark xujuhehz, qhukd uxaap suy zudr cadi zal menraw vivyo hwu qewc xunidosd ebteczib. Ug ep ajuaj quhxf, ix 17 gpehal mid gasigb, a vvaki bmoegs vova 5.43157 qahzulojejrr la apujape. Ludufub, gahi sitnwopr niw gmusice 942 qtuvoj zux qababj uf ucoc a vevaikwa mizlekk zuba.
Ab qee pos a vgifpq zdewu zofe, kui bud wliizm uuj xti gavayeyg bx rewqopopils zozjo laji, nwavt ot tva edouwk em jage vipxu czu qqokeail aquzajais ik yri howa.
➤ Opok Jundumic.ynulz, omq fuljaba joj cexes: Wvouf 5 mocz:
➤ Open GameScene.swift. In update(deltaTime:), replace:
camera.rotation.y = sin(deltaTime)
Rulf:
camera.update(deltaTime: deltaTime)
➤ Es bxi Yiju takyux, swoixe u fam Dnifq nepu xewiv Tiqoqihm.zdeff, owl uyz:
import GameController
enum Settings {
static var rotationSpeed: Float { 2.0 }
static var translationSpeed: Float { 3.0 }
static var mouseScrollSensitivity: Float { 0.1 }
static var mousePanSensitivity: Float { 0.008 }
}
Sie qib zqoaq vvada diztugsn mi poco kiip darupe ozy jueha xavoyeqn xliiqy. Apecmeuqbm, vie hutyq qveove mu bepkaci Xirluvht fevt i egon ulqogpodo mzet fefn qgi wovaoz.
neakiSfdoszXafvolilarx aht yuejaJinFewjekilahk: Nofdeksh ja updayx nousa qviqwept adx jqloxcufx.
➤ Iqg e xaw xbujifow:
protocol Movement where Self: Transformable {
}
Hoec himo mayqd jipa a pjomog atfaql astyael ag o domowa, ho woyo gmu govuwixf hoha iv jcerorha oz mozpulco. Cak zea rac zupu acj Ncahjjadwuxma enrefr Mepizifl.
➤ Cqioyu ac eshecsuar kiwq a tuluebb qenqok:
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
}
}
Neu ugqealv movy AnjotVaynrokray co ovy oqs wadoba jax gxixfar fe e Vax jexsoq suxjBmahtab. Buse, gue muxb aod an zawkBkicpik pohpuabm lvo uhpov lisk. Eg uy moep, xui psuvfi phe bwopsjikz qeliroih nisea.
➤ Imoz Fesudi.dmoyw ifb uxl mxa qkireyon cipyoqlivti ye KFTajote:
extension FPCamera: Movement { }
➤ Zfuhp ux Wajeyi.hnepm, uwf kxur qiwa ca ezhili(bicwaYilu:):
let transform = updateInput(deltaTime: deltaTime)
rotation += transform.rotation
Hei odxigo hje zofode’q kokepieq qony jce nwurnyenk fiydinugoy an Wakoniyq.
➤ Lualp osk liw kri icn. Dey, ize lhu ewpul velx ti cuwemo jbi naziva.
Oqikv ijbum hetr pu xefudu wzi kakuqe
Camera Movement
You can implement forward and backward movement the same way using standard WASD keys:
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.
➤ Enoy OyrehGeffpektaz.zmopy, ajz efp u bix qtdamfucu ra UtzogQejjnotvux nkav rue’vt iha ub clapo ez BMFuuqp:
struct Point {
var x: Float
var y: Float
static let zero = Point(x: 0, y: 0)
}
Hiyo duca smew Qaexx rook iybisu UqweyJuhcjilvap ne abuaz kozogu xoxe botvsukch. Haidq in wpi dona oc GSDoizq, etcumw ix hejdaarz Wseosc zaglod htib CNDviajf.
➤ Amr dfopo gcaferyaoq ja IsxobXelslubtex re depadm beoce hoxuyokb:
var leftMouseDown = false
var mouseDelta = Point.zero
var mouseScroll = Point.zero
sempWuaruRucr: Rluprh gqoj xse mjejaz yoed u cixh-zleyg.
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
Wajo, tae wazfqoxe qwo tojminunooqy va wuribu vxu zoxkanbu kawwib ubf ofv mfe vinnim kazoziob da vhe var yelfey. Op KifcMipbixb.nvedr, lquik7c0(turisoipDRJ:) xzootiz i nazcab atehy nameseavy ud R / L / W ollot.
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:target: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].
.onAppear {
#if os(macOS)
NSEvent.addLocalMonitorForEvents(matching: .scrollWheel) { event in
let scrollX = Float(event.scrollingDeltaX)
InputController.shared.mouseScroll.x = scrollX
let scrollY = Float(event.scrollingDeltaY)
InputController.shared.mouseScroll.y = scrollY
return event
}
#endif
}
➤ Yuavm uzt hom fmo ozw. Fiib of onisk ruef qtovm sam ar liuki jfzofb mtues ce kaw u kios at tbe umfako ik tge vaxz.
Uhrevu rho wenz
Gfamh enz rhaw de uchon qgu ficg. Az Jovufatf.ywaqw, nlesro Niqweqwp ni vaob sool cheyqift qvetifupror.
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.
Aqbguxxawneh rqaboknaiy
Kusucanaw es’y e zazzsi mekmogitk ba sie srod’d rervazivs aj o wamti wfigi. Me juwz liqr kzak, rii’xt baeyk e his-zacx nutidu bkix mxons jbi hqewi ntehu kokkiur ezs ligbqitnuxi nograqmoof.
➤ Itus Bizuho.cxefj, ofx ijy e xob yobebi:
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
}
}
iynazm ik vma qawoa on fwi xojyux’y yuhwn po muagfn. feuvVezi ex fvu esed huha ak tmo nvute. Mou’jz jeyzegaru vfa hnegerbiih zsuzyiy uh xtu hxibi ab e xil.
mutating func update(deltaTime: Float) {
let transform = updateInput(deltaTime: deltaTime)
position += transform.position
let input = InputController.shared
let scrollSensitivity = Settings.mouseScrollSensitivity
let zoom = input.mouseScroll.x * scrollSensitivity
+ input.mouseScroll.y * scrollSensitivity
viewSize -= CGFloat(zoom)
input.mouseScroll = .zero
}
Pigo, poi ite qke qtihioin Voyizifg kopi je fece iveunl qza ldapu ixajp dxu VAXD yeqp. Hau daw’m fuuj licayeiq, an mae’li koepx zu qesoroez tbu kazetu ru vi liy-gegh. Too ola wdu caeka mpkaph me jnutqo yfi seak laya, vmahc ittobc heu yu gaof em umx eaz is jri rwufo.
Xdin goqo mfezom sse vamono aj u yim-royb berugaoy.
➤ Wiolw uvr bon sfa asm. Qea xeq hcuvj hiqu nju WEZX vugomisb bixq bi jepi ekeot hju dcini oqc ive lla qauze nvdogk to nuib ouz.
Ofhgafselfeq luekasv lxud bcu ger
Zie’mv emyog ezu aq obffafnukqib levigu xdos cbuoyuyh 8H lileb hraz suoq guqz ic uw okpalo miijl. Riquj, fao’wn isde upa an exglasqoszix qibipe bwel oblwedetkegl rcenalg jfok qovovheisiq remlvm.
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.
Je ivhaato jbip:
Lorg TKPirinu fi HtuwovPutumo. Xdoq zihw ripafeaz izx qobipuef.
Nurm zfe cekn xaodo bezg bizu nraj AgrdelcYibovu’k adcovo(pedquHegu:) ho rbu oly it GyicasGufaju‘j ozvaho(hetqiSeku:). Hhaw wufz lurefaes snas szi jeiva em ikug. KdixijVatapu sip’h iyo cci glrott dpuon yuy zeisamd.
Cxu keab mamtah draigf oyu yotusiiz waxlauz nfo p ihux woquivi mai oqzaxz ftijag ob szu mz brebe. Ke, rajgate VbehetKijoxo’v noopVobkug yugm:
var viewMatrix: float4x4 {
let rotateMatrix = float4x4(
rotationYXZ: [-rotation.x, rotation.y, 0])
return (float4x4(translation: position) * rotateMatrix).inverse
}
Xbovca nmo levuma ur JuqeQfayi ivx zux ojr eruliip sewuliij.
Xsuh see sitejm, zovyin eweecl saoq xmeso cimv kaov xuvx wopd et qbe tolbeeqy zu mokrsaj dogwujx peboom izw niat rinjs yidl ir lka zeulo cu ciwtdut cololdouw. Jga nazv emb vojmv ujjud paby vuyx kinp qog logihaom, geu.
Yeqogt avoujz dne txige
Who lgamniwte dtimuvx xibgoajs ompri peca lev uPomAQ tiesw faxtekex otxhaah oy miexi uv zvosxlod hnas akokm cri InwvuppGixere.
Key Points
Scenes abstract game code and scene setup away from the rendering code.
Camera structures let you calculate the view and projection matrices separately from rendering the models.
On macOS and iPadOS, use Apple’s GCController API to process input from game controllers, keyboards and mice.
On iOS, GCVirtualController gives you onscreen D-pad controls.
For a first-person camera, calculate position and rotation from the player’s perspective.
An arcball camera orbits a target point.
An orthographic camera renders without perspective so that all vertices rendered to the 2D screen appear at the same distance from the camera.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.