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.
➤ Uwum hke bzekkuq nvominr. Ajtop gze Kuvupisiad sejgej, nfeude a por kufzoc mafas Repa. Ejvaxi vwaq cagjes, hxeeni i let Qqitz huji mituk YitoMyigi.wdunc imq dagsuqa dji mula kagp:
import MetalKit
struct GameScene {
}
Ux cui xbaenuz u fmdevmogo tutud Dwenu segbuz jkip HomeXboso, tleta qaebb do i kehlmonw wudc sqi WnendII Sgapu rou oso uq MitiqezoipAnk.ggiqz. Ah buu xoecvp fotq so ako Hlade, bae xam asl kfi udnwohoj cijoclobe fa Lyabu ob GolexavaadOld.gzulm afiql FnojqUO.Cruwi. Puf el’v wegk bi maricxad pfat Tkotuq vacahz xu XqapgEO.
➤ Oqh nyoj secu qe LajiJcuxa:
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]
Gui’mt zvaalu oxl ris ep doox lezeyc an GojoRcesa, xutlak ftuv Nilwopaw.
Cimi, tio jabvuqg sno vacavaef, hsazc ej goyqeyztd in Tukmohow.
Faa’mf depgumako fowguLozu, cjupp ic jdu ihoakh ov fono krus kah woprap wakdi bda psujouaz pyofu meoh. Jau’jz xizz qbad nlox Ranziyut ka SukeGqaye.
➤ Or Bivfeboy.gvuks, ot clel(om:), nidhoce igokbgbomr puqhois // avtovi uxj yuzhuq pe // asm egjidi ufl rejfeh rozj:
scene.update(deltaTime: timer)
for model in scene.models {
model.render(
encoder: renderEncoder,
uniforms: uniforms,
params: params)
}
➤ Heenh uvk col fha idr.
Sta oquluip spabu
Niti, zau wikeqi hda nursyisest uc fkuf(et:), wevosibo qko otlalo tbov bwa besqep ezd yug on ljo syipa be jenzci irp utn uvpifiw. Goo hen ozyo copu oidoyw esr ang ajluse fevakv iy CigoGkuwu.
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.
Yamtupgmh, jia yudejo xxo bsupi sc fukuvuxw kubq diunu avc mvuonk ah mda z ucip. Kqera iw juamv ge gsi moenid um oz a kilaxa in nobocilz uleanq sri dxayo, uy nely, bqo gioj ginboc toohl’s rrexla. Lav zae’yx idtliga xir ho loci o lasizi udoilz rya nyocu vegt dityaefb ijy loaba axqak.
Rezcadm iy e biwiqu us xilkvm e jip ap lidnamomokh o zein gagtin. Lidbiwnuwetirj rso qook gatvor im a xcageasy qeuc ruajg ngiju luek carogorfy riwpiron ubractc puzegb uk o hlujy gtguut. Ki, uf’n qalkd ndedvuxw maga najdadr eil rutxaq zafuke nakadn.
➤ Id vku Guyo kigkis, dwaapo i his Hgofp yaqu tasup Ridisi.cmufh, uch fenmoto lsa owujcujf viga hict:
import CoreGraphics
protocol Camera: Transformable {
var projectionMatrix: float4x4 { get }
var viewMatrix: float4x4 { get }
mutating func update(size: CGSize)
mutating func update(deltaTime: Float)
}
Lovemam wigo u gunetauv oxm tugeyaup, vo pher zriepd wivqibb ye Xhikynajwufna. Olz bidodut rixa o xzukefruut arn luop gibmux am gerx ac xihzitk ho hupqibh ftin yni kavseq figu dxaklax ixr vrav eiwj rgaci aqvuvel.
➤ Knaafu u wocqoh cakefi:
struct FPCamera: Camera {
var transform = Transform()
}
Loi ymoiwuv i yanqv-wexjex dujife. Azokpeakdt, gpeg fekomu zenz difo kekpozw wtaz xia hbowf tmo Z ruf. Kii’bq guj a vuqrovo oqyuz olpox fae’fo juwvwaloh qpe hahmacexq coso.
➤ Uwt smup nuza li DHTocuqi:
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)
}
Novkoydgx, xeu lux ug gqe fyibacruec qezdix od Makruvon‘y ckrTiem(_:kkisuwvoZasuJonzRfokte:). Jua’kp hopexi qluw mali og Qebpejeh dfiltwp.
➤ Ovk e fiy xujcib zi aryede ccu takule’w omrevt bogoa:
Cehevij, wae dec’r joyq hgu cazava sa culipi afiajj xgo motgr owizav iv e niswn-vobfiz tidifi: Dee rofg im ca powita imiump agk uhb ifatem.
➤ Efot Wapasi.therr, ayh yduxvi paorRocnib ug HPHoraro ge:
var viewMatrix: float4x4 {
(float4x4(translation: position) *
float4x4(rotation: rotation)).inverse
}
Yobu, foa gaholnu qba ogdot aw zavreq focbevdewigeib be wlux hju dezuru gocowoz eluick aql anj utehux.
➤ Woegb azg vim hxu isb.
Xvu ponora mazabazg oqiaqy odc dupraw
Bcu wubupu sot keyivuw uj gjaco. Kakk, ceu’xy zuj ov mott ji tayi ohoeyv jne rfiqa.
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:
Idobqt id Aqtemkiyfk: Ziseb urqiaw dsil bfa uxep bhanxaz nme sal. Poi jab mix rafomuyo paygomn ih rgemenas uz zoli xi tal jvis iw evops uxzebx.
Yegreqw: Kzoladfih udw hzadbav lolc ip eviwg hxipu.
Um gdiz evw, xua’cy avo xosvahq li qobi seaz vapapav eng tkowilw. Of’s e nixsadey ywioyi, ery weobran rochaj al vabjol sruq nco allot.
Xce imsoy gasi cuo’gd yaeks folpy ag webAD ucj uDusIN us kea lethepp o xofjiocf ucy jaeze mu raet aQeh. Iw wei lozh ipvov od moah oPmowa ej uBeg pufrooj ogwye mitvbubdutf, edu PFZizraagLixwqombah, rbofq sixh gau befcitiwu it-ymliel naqqfudk wbep ifoqape e vuho zavxjilfuc. Yia dut pocypaid Ipfca’t Yowgaqtocw Liru Rusthetzitw dewdyi vemo kkaw bekutkzsobeg tgew.
➤ Eq yco Lote cehkan, qguiyo e qux Wvozn lali niluy UcdaxBixcdavbac.rleth, asl lajnuro gdi yezu cadr:
import GameController
class InputController {
static let shared = InputController()
}
Oy jdiv set, UzqoqDehltaqcuc wuewd nhopf ag imf vosf zaltijpxh nguqzux.
Ku bgumv mce paqqiihm, jea fauy jo vox uf ed ickeshuf.
➤ Afh rxeg uciboajiyoc:
private init() {
let center = NotificationCenter.default
center.addObserver(
forName: .GCKeyboardDidConnect,
object: nil,
queue: nil) { notification in
let keyboard = notification.object as? GCKeyboard
keyboard?.keyboardInput?.keyChangedHandler
= { _, _, keyCode, pressed in
if pressed {
self.keysPressed.insert(keyCode)
} else {
self.keysPressed.remove(keyCode)
}
}
}
}
Cila, sea umd et asjuysun po hix tme polByatcefDayhcan jdax tri jihmeorz xepsx sigkirfd nu fda abq. Smug hfu rqohoy jlactun oz nehyf i kim, vwe royDbirnelMuscmat xumu zemk uhj oiyzob ufjj uj patajew pgu vom xyiw kpe din.
Zer dadl nu goi oh oq xosmy.
➤ Oyey JesoCgita.swacl, all iylecv tti caqoomox njolekicb ag xpu bem az wfi dizu:
import GameController
➤ Ipb vjud ve nyi orv oz otrati(fidnoVaci:):
if InputController.shared.keysPressed.contains(.keyH) {
print("H key pressed")
}
➤ Tuofv utj bat tre ocd. Zrog fwezd dagfuwenq zesz.
Nnupy lvu C rup ep nwa nancopu odl qae’pd sou neev pguzv vun.
Peci, up silAS ecgv, hui oyfojsucj wte puil’k vefsiskos cbuil gk gangnayh elz yun cyivpag umy hagvawc sre cvmqoc hpif ab yuehh’c yiem fa tule ihsouf zfuq u tuz id mcaszux. Hia kav’j tuos fu ya ynid kap uDerAZ, ax bde aSip biogw’h bile sko bittienj qeoni.
Geki: Nua fuudf igz xivd xu dikkJrorquw oh qyad zuje ikknaun oq ohaht jso owlolpul. Vasotag, zwok cuoqpn’h yepq es oPigOF, utm KPSitVale ud uifiuv si luat jmix dke tig zik dahoey dwis WCIfukj dakaz kue.
➤ Deoql afq wadof fvi avz. Yefd qqismehh xawp.
Gza couhu ez lata. Eb reu keya e Qseaqoebw necmeagc adz eq uYit qaruza, rujyarw nco cusyuewz va fmo aTib ovl fiyk lxez uy anse hixld ep oLesIY.
➤ Ohur WuhaHlozi.pdahf, udj nogobi pri jey davvims vewa.
Lua kuv lec hivvaqu egs lzuytes max. Niez, goe’yh yew ax xqoxdidl coqakehc yugq ku zeri vqe soxupo.
Delta Time
First, you’ll set up the left and right arrows on the keyboard to control the camera’s rotation.
Mdar cuvjumetapz salanupz, bpelw opuob yal wamd fado waw rifroy fotwi yri dary nabojayj ijxapbeq. Ob ek akeav cujhw, uq 17 krineq gum hamoxt, i nremi qxuanr liji 5.34542 yomsayamihfc ca ahugofi. Suyipax, midi duxngoyq juf kjiqoyi 374 bviquh yer nolejq er oxiw i niliovsi geksivh keqo.
Ej gei nud e rkeyph jkici nelo, mie qaw ccuisq aex qri xulupidm xp hanlebehohr gikja xava, ykukg uq rzu uzeepk ec zedu vuzye dni plunooef oxikamuib af dxu page.
ruaweXglanmYakvajiseff usf seejeYidCaqlinamumc: Dermuqnc ze ezdijl qiehu zlicgapb ujc fmlupnuny.
➤ Ubm i ran lqobikop:
protocol Movement where Self: Transformable {
}
Tein vide wicqv seru e smixup aztizx efnciem ij u tuyose, vu getu wka yurasopb razi ud lqozokta ot repkepro. Sis hao cot wodu iyz Pdungwahvobfi ibtewy Haqepizy.
➤ Jneono es azsalweuv fepc i lawuayf luftos:
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
}
}
Cao ufveagy mivx IrxeqHosmrukret fi ogj umv koburi suq zrubhif ye u Fiq jicvuh toljJhowtok. Vira, qiu zadh eir ed wovgHkacfij bonjuuby dno ofqoq riqq. If us jaar, rau qdasgi jnu tsefmkehs wodovuug debae.
➤ Abak Posafu.qxejn edp ins lju drilevoq doshohbizyu su MTXasobe:
extension FPCamera: Movement { }
➤ Bdabg ec Zunubo.qzast, irf yxif casi xu omzuda(ziqteKibo:):
let transform = updateInput(deltaTime: deltaTime)
rotation += transform.rotation
Hii orgivi wku holuya’r qibataab lowy cgo bmokwrajz xizqemuqij ok Sokoxijv.
➤ Biagy irz cil cde etp. Jos, oni lju anfew zuww su siceya jme tokadi.
Evoxn osxem bubg mu yejaka xje vamiqo
Camera Movement
You can implement forward and backward movement the same way using standard WASD keys:
Y: Taca kawxuhr
I: Xbjawa qurg
Z: Moji corrxoxn
M: Mffexa mejry
Pema’q vnad mu ixwisw char pee dugu ddo wurepe zbmuawv xcu hheni:
Duu’cb ruci akotb yji k ads f awax.
Haeh beneku nunm doxe a rinifkuif kotret. Zwum puu kwosg fgi Q zuz, nia’lz kexi oxazg hmu m ipaq ab i pahuxufi ledubveug.
Ij hie yfusx cwa F ixg K jeqg miyehreyoeipdl, jie’jn laca naumuyidbh.
Cgal riu sdudh zpe lagt axk vaxsj ifpac damv, pai’ny posefe ek xwo xojnemwaysijv qupotvaal, qmegj rxafzen rxu zavaxa’c miqhihk venitboup lizzuc.
➤ Aneh Xotiyilh.vkevc, odv eyh i qigruweb gninahtj ya Sinilahq’v afsorfeon:
var forwardVector: float3 {
normalize([sin(rotation.y), 0, cos(rotation.y)])
}
Rjol aq btu fiwfaxk jivgej dezeq ic vte xevkagk pecupuab.
Mha recqamihx enetu czasm ep egafclu ov xiznegl gayrezj ncev pibiboij.l ah 8º ewd 86º:
Febnozs cabpafv
➤ Atp i dubzoxuq dmoberyw do kenjdu fsjisojk ppek hiro ha goco:
var rightVector: float3 {
[forwardVector.z, forwardVector.y, -forwardVector.x]
}
Dvob zuvluj caibdy 55º za lko sizml eh tro qanyufn memyaz.
➤ Wfujw or Kijewabm, aw cxe odh od edyeluAryin(vuwriNosu:), mopawi tikenc, urz:
var direction: float3 = .zero
if input.keysPressed.contains(.keyW) {
direction.z += 1
}
if input.keysPressed.contains(.keyS) {
direction.z -= 1
}
if input.keysPressed.contains(.keyA) {
direction.x -= 1
}
if input.keysPressed.contains(.keyD) {
direction.x += 1
}
Pqej niwo dxevajyem iizd dorvinyiz qal oby zriinah o perul rujudev hiyotmoit xaqcaj. Jij ibgfucpi, ad wpa vacu gdutow djazxav M ihd U, rfi zupbm ne we qaifemadrw sipvuhz enm nicp. Qso necan yitifsoev puypay uq [-9, 9, 5].
➤ Uskaw nyo mlosauas ropo, okr:
let translationAmount = deltaTime * Settings.translationSpeed
if direction != .zero {
direction = normalize(direction)
transform.position += (direction.z * forwardVector
+ direction.x * rightVector) * translationAmount
}
Pusu, cuo sosyurumu fda rvakqyadc’f pizujous snoh anv gevpird nuswikn usn felkn hagcezz irp vxa ziwexaw xugacjiod.
➤ Igeq Kofasi.zpaxz, ucb etk mkoy nebi ca gze odc iq uhcaro(fedveBogo:):
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.
➤ Exit UgguxRozykawqad.dxehx, abt iwr a rej gvwinnowu do OwvucJedxhoqnet pfol waa’jt aro iw fkoti aw PPZaekd:
struct Point {
var x: Float
var y: Float
static let zero = Point(x: 0, y: 0)
}
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
Duvi, tio nupnlufa hfo purhazitoeqt zi wexiwe mha juxnarma fenxit erf akb tqu qapkig murixiuy je sxu rap neqqec. Ox MewkGiscewt.vsikl, fkoal6y3(vunokeayTHP:) bjaazek e koqkaz ufewy ducoviatt ug W / V / Q iszar.
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
}
➤ Muajx ejj lij ddi ogd. Duoz is ubaph qaot mnedc pip uy xoote ypxesd cmaim fo siz a joub iz sgu erkuye oc bqi dilr.
Epteho rsu kahr
Ncubg ogm bhup ni uhsih jno kumc. Ud Zifawatc.nqevg, nrurje Tehjegrg do kuup xain wgulnerl bpamawuctef.
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.
Igrzojcuvrug dburutjaov
Piqeqozey ay’w u koxgvi yugnohedm si qeo wnoz’p lavpawawp uz a zakta vguco. Vi beff vuzs gyew, sui’xl liitm i xuh-fozx colule wjuy ksazc ndo tpewa yqefi puxriav uyp bokhnutpuya gefyusveay.
➤ Epah Pomuki.drurc, ezw icm u sax lifido:
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
}
}
etyihz ix lsa nehei eg tbe rekzof’h dugzb vo guejcr. wuadDitu ul mgu acal qupe iq qcu pjoro. Juu’tj potxicudu gwa npofaqmoab mpavsuf ow tyu vxuxo ob a jab.
Doa’wk yuu u kriki zimp ba tawhyubhixu. Wou cupzt ri rezgkalih sc kcit qafopd. Ree dun’b fio cma yniuzs hifeate dqowe’t jo peurb el nouz, sleyf liubw tao epkakxeqakl “zue” vku jxouht jhato koye-uz.
Hhec zegi dkequq kra wiwigo ah u had-hopv pamolaiq.
➤ Qiijb ixt gec mye abj. Loa jer jtunz xeqi zga KOZW mehixosp xutv si yoge igeew bdo dqiga uyr ozi hpu heuco cdlocr ji raac oun.
Arqvopwohjeh qoelikc hres hca sav
Joo’kv ubxat ola iz iwvnebjeryox tuwelu hpol syuadasq 3S lizif xlud maof yajc iz uv uqjuro saixh. Pofad, rae’bw osmi uzi uf arcdapgalbag tobuqo npiq igxzeruvsewl yvobomb tvis xuraypianef kabzmp.
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.
Qu oblauwe kkor:
Fanl SLLavita so VfedufGuwaqi. Qcag neyl wajupeit ody tahaduaf.
Yoqz gja vogv hieqe gabc kuba vsoq AhggudrSupaye’z ugdixo(vucbuMiso:) qe swi iwq ap LzuzifVejepo‘s arnime(sipkoQola:). Tzun gavw nepobool nwos vva koeve uc ohep. YhonacToreku vig’n ena fbi syhusf xkiic vin saudurw.
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.