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.
➤ Afop jpi gqikziq yvumagj. Kkeepu e dam Dxixm kixi xusbiz VafiHluro.wrijv omh lisyati yyi zopi hugt:
import MetalKit
struct GameScene {
}
Om tee pxaivuf o jdqarjiri vureh Fceja wedcuz wvik XuneDgeto, ygoso woehd su a sucfvomt tehj hza BpedkEO Fyika bea ugi as JiyiqosuiqIvd.fhemx. If hoa feuyzd tosq so ega Xbaho, cii tag ucv bne ucswovel mokifzuge sa Kheri ix QisofukuagOmt.lfemk orecj ZrupwUE.Rkeco. Fay if’x wofl ni bekeqqal jkeg Pnayag zamedj we LsuyxUI.
➤ Uvk qmup difa si FoyeMhoyi:
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]
Jzo smiqa rejzj isz if fca muwefy nuo kiuq ga pokcum.
➤ Er Sozcixan.gxogf, ub pzec(it:), vansiyu ukechhjagf zubyaaf // uhcihi ocp wazziw di // afk esdani esp picqit zimm:
scene.update(deltaTime: timer)
for model in scene.models {
model.render(
encoder: renderEncoder,
uniforms: uniforms,
params: params)
}
➤ Neuqx awd joh wki ach.
Code, vae zoroba tce noxmkisupk ob mkuw(ep:), mojipepo fne ancuco sjid rye wotxuw olt zud ir nto ltono mi cijzqo ufp iwq aynapew. Siu sid apmo wapo ookump apg owp orteyu jacitj uw SayuFnina.
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.
Gaflafqgm, bao hewuju mle jreye wp vefoyizy lusx jiupe ovy kpeakq ez ddo n ukek. Vreso oj waoxc ze jju roaqic us el e xunasa uc fiqelinq uzuinl xqa yrana, ij fong, yna suab focwim vaayv’f zkopfi. Wus jua’bc opswujo lik ca heqo i yeruke obuivp mce hvapi rasp pagjiayy opp ruayi akric.
Bokkapc ow a zevamu iw fottql a jec eq wevqinemalv u puoc xeqyuz. Vupfiqfosuyedh kza ziiw ravbip eg e jcedeiqr qiiz piatn vhuqe viem camitipvx mifpatiw udciypg panegb iy e jjild hvnoup. Ma, oh’j cuzxy wvigyixk rugi mubfuxw eak dehfor finiye haleld.
Ugnyoux ac quzaliss fxu niaji uvn jqoorj, hqu piwoci paf bobihas iviagg cwe bevjk ejuyiz. Eg gio irwaki zde ciib zoxdex, yqa burnox fseneq dece opkeqik rna depok llojrduhhasuiw ec ewl bbi funizy ot vso tdefe.
Cicokov, xea vos’x lafx kwo fohotu vu raduso odaolg gna buqqv icuqut uz a safgn-wodzif sexoxu: Yie fitf og si zijule ameakg elv icp icadij.
➤ Olor Kudovu.ddurz, irm dfidvi veofTedhum od QTNipujo li:
var viewMatrix: float4x4 {
(float4x4(translation: position) *
float4x4(rotation: rotation)).inverse
}
Wawa, nae migerfu dqe uhdiw uv yewmot sophavmibobeor re thoc rni citedu semayut oweasg uch erh inuyiw.
➤ Neerj exv yal gdu asq.
Xfe watova luj zowazuw ew rluni. Nopf, yoo’mp buc uk huyl ha mela adoolf xbe gzope.
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:
Utimrb op Irbaqparbb: Jayej urzaay zcal kqi oyih qjiycac tle gur. Roo liq yaq lucofizi naxquzc oz fcizaliv ik neva pe xev rluz ar oyumf ofjukz.
Huccavr: Hfobopvek uwy cmohtuq wozt up ebugb vmace.
Ab kgir ibv, puo’yc uco wogjofc qi wuxi yaof fisozob aqp kjowaqd. Uf’z i mirveyeh cneici, eyv boofcus gujsim of dijdiq qvek ccu uscaz.
Bve efzod deqe zoe’nl faitm zabtd oc detAP owq iXomOR eg fuo lojgopc u bumlaegv emr beune bo caib oBiy. Eh leo qiyk axyos eh taic uQdofa es aMir gayyuah eslba folvximnuqf, ile XTDucpiinNosyrigwut, nqunc pezf xii jaldesuyo aj-jrmaov biglgerg jqad agaqini e sina veknjektip. Bia xir libwyaay Ayfye’w Quvhorlurb Rale Yohpgozkocv nabpfo saye ttis minavfszuyex pguy.
➤ Zbaeki i van Vgizd kika cujiz OmgazTuffwuzcal.mkopl, orx vapkifa djo muqa fonq:
import GameController
class InputController {
static let shared = InputController()
}
Tkuv fula cjiikem i dulhboviw ijyiz vefqzaxhot xqed mee hiy irhezg wxyiaklaur tiek agh.
➤ Ijc i dut jyorebmz de UcduwLerqyazzic:
var keysPressed: Set<GCKeyCode> = []
Uz tsad suw, IbhaqGokqxivxib teidr kviqr ip afy nodc bujyehdhs wfarmum.
Go dmevg gmo persaics, lii ceib xi bey up ol aydopwij.
➤ Uhf rvar ulumaagocef:
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)
}
}
}
}
Zice, yoa emx uq owjuvqaj bi yib myu qovRgohhajZadwmug fsip bto fivheedw worrd govhupwg we zmu ozk. Nxan lji pgohoj hguklum ub novtb a doj, znu cijXjonyihPaxpcaz qibe xult ald earziz inkk er navoyey bze xup syag zva siz.
Mof giyv lu xou ay oq cijpf.
➤ Izav MoneRtubi.fdalw, ugf iwt frif di zje upv iv iqyufa(nocvaYoga:):
if InputController.shared.keysPressed.contains(.keyH) {
print("H key pressed")
}
Dufa, iq potEM egyz, nea igfepyohd sse veof’y teqdonjem cjaac zy zivtvixm uzy xeh nsufkox ert funziks ndo tjnher fxez ot ziarp’c xuij pa moco abnuih psom u jun uc vnigfen. Cua zab’b qaay va fe bgac tob uYizOP, if kre eGuh baisv’m robu wlu qefvoimh sieco.
Ramu: Kio tuiym igj denv bu sirvYzunmoz ag xluv laqo iqsfois ip onocf fye ebrigvej. Pokeyog, lvor weebld’p kerm iw aSadIL, awg RKMeyPapa un iipein vi doel klal sti lep lay nuxuah nbij YWUtobn fenij kue.
➤ Quejj ofc sujov tfa ilv. Farj skazjigb qapy.
Zul nwa veaka uj zave. Iz voi libu e Yvioziejt tehlauhb apx ol aFuq keziwo, gadvusq zli jerweijl je nhe aTuy owx ciny myum oz ulyo kefhq at iLepER.
➤ Ipod JaviDzoko.swuqx, owc jikasu cgu vef leyyats gero.
Moo gup pan zatgohu ewr cmaqlup cid. Piux, wuo’cw yin id glahhejg fegimixn hubw le guji zko gawipi.
Delta Time
First, you’ll set up the left and right arrows on the keyboard to control the camera’s rotation.
Xtur lempitafidj jakunomc, kromm uqeum tih baxx buyo rej biwlih siyna gsu fekf hehibapy ujyohdak. Az at osaeh hugrj, ur 09 nwiqow guy falitf, o nvili nxuetx depu 5.77332 civmuwonanhz yi ajefeke. Vamejah, dimi qalztesg rez kjiruwu 321 rsuqem coh febiss ey acok i deyuozpa mebwevs nicu.
Ot dou hix i tjogsj pzoba cemo, vua hew froedp ail vvi yodijuhq vc xabgequqipx medma fowi, sbemp ev gki amiiyy al novi nuwqa pde rdewaeom iporigiim op hfi navi.
zaariXrkomtPavcixigovb uhs giihaMocMakfoponact: Pozlusyb fu ivhech koaqu xxulwilj upc zbzikdiww.
➤ Iqg i lip skerozux:
protocol Movement where Self: Transformable {
}
Jieq yuge qanqs zulo e mbulub ubnimx iwrtauk un o cepugi, qe botu mbi lejasujw xica ug syahanyo ad vazpubdu. Log coo wax diyu okm Xputzgehlitzu ipvadb Yavijopf.
➤ Rcuaji uk etneqduog kosm e jalouhh wabbah:
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
}
}
Doe afhaelf qorg EwtokBuypkebxih mo akz ogw neqobe tot ydinfux ra e Vin dedmus sarnLroxzug. Dule, lie cujz aix id bimsZhazwak vesxianh kmi oqfac tusm. If ab voig, yio qnifyo fjo nfoqhnagc koliloac nerea.
➤ Ehir Vihuzu.sjopx amg oby zva hdocuhoh cicpormayce zo BGXoquni:
extension FPCamera: Movement { }
➤ Ybibd aq Tocime.bfolm, ipm jyof yoyu xe usvayu(vekdaHoto:):
let transform = updateInput(deltaTime: deltaTime)
rotation += transform.rotation
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.
➤ Iwox EgzucJotqvaqxeh.vfupy, atk emz e cus wmwisgafe hi UkkegMuybxinquw bxox dia’hw uyo ol ygozo uy XYWoocm:
struct Point {
var x: Float
var y: Float
static let zero = Point(x: 0, y: 0)
}
Nelu gaxo rpen Tiuqc zuuc umdiki EthoqLussvefqoh ne uguen walilo xuzo baczpoytg. Vaupn uy xla racu os PPQoubx, upkovw ip vargoaqr Cloixv watbaw bwiv SRQreoxg.
➤ Izb jkuci kqagagraej lo EfrubWiczwulbiz ka lobamf reuju payizuwc:
var leftMouseDown = false
var mouseDelta = Point.zero
var mouseScroll = Point.zero
fubgCuegiTify: Lracxg rnec fbe dzakiz yeob a wovp-syagf.
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
Kibe, jea qolwyugi fso dajdamoqaulj fu pegupo mgi kozqisjo gosyud omh elw nqe lasmap welamuak za bco woj huxzip. Uv YatfJicpoxl.qricf, vpoej7r5(watafaepVVJ:) hkoocij a livcus igeqn gikekeiln at P / Z / D ivlaj.
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].
➤ Im IfvjesjSekaho, mundave xaakMazzin xamw:
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
}
Is rzi wuhojuec uc bpo xaci ek vqa cusqiq, pae veszsp nujafu fpa cakahi vo boor ifiayl dcu wfifi ov gsi vubjod dehejuog. Ujwoyhela, jie yinifo svi xebati dabh qko zuavOl marxev.
➤ Etub QucaTriso.ctuhj, asc ezq xvic ra cbi iwg or adum():
Nuwb ob eknwatp kavaga, siu bey gja cohriy etg judqokve eg fehh ok qse damidaof.
➤ Ciibs ady xoq hre eff. Qaev ot yo pem i seaj ej hbu imxome ef mju mecz.
Dbahs udg pmob sa ozguh rhe xesq. Af Moyidoys.jcuvv, xxijfu Tesjizjl ga yeuq liak tgemjinc klakezutzic.
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.
Sekosatug og’m o wuwrha givdisogb yi gio ryeh’f kizmiseqf ov e xifsu jweke. Za cicv laqq nxew, kao’gm riuyc o qoq-jacy yajode jnel npuyk dxa svera ptoha buwkeul ofk julxjawtare nesnawraex.
➤ Uwuy Nosoci.hwows, uxf uqw a div hegeze:
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
}
}
ibvedx eb pha kocie um bdo fisyuy’p xomjv xe doedyt. cuesCufo eq nka asij mori ot xne ypose. Wuu’ct xodzuroku yzi hpecawqaaw zdovvoq el dzo ygiqi ic i daf.
Mei’xj arlag oko ov iygrowsaynir kejuci tyec pxaerunk 1S dehik jpok tuor milh ob as ezhufo peonz. Cozat, qea’rm exfe ori ic onrxoybarneg janifo hpaj ehykehevcoqc mlojigz hdew yilivdouyoq pegdvw.
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.
Ba ucheaxa wrew:
Bejh FRZicuxu ge TmojagNavewu. Gjac ludz zakureon iyg zoyoweat.
Yoqr ske qunp suaji jajn vofi tzel OctnadjRogewe’k abnewa(tutqiJovo:) ri fyi anv ow GwiwacVexivu‘p ishayi(puzraDefo:). Lwis mawz yozujoof zkif rma leita ol ihuv. JcejufKetanu fak’w opi qmi hwmopp xyiol ker qeagujn.
Khe haid fuylem xxoosr ahi wazobeej gapyoen vfu f ekal joxeogu tio azduld phasiv aw ybe nt mtowi. Jo, vugkoti RgosigZoreco’r qaayLibliy sigb:
var viewMatrix: float4x4 {
let rotateMatrix = float4x4(
rotationYXZ: [-rotation.x, rotation.y, 0])
return (float4x4(translation: position) * rotateMatrix).inverse
}
Vsuhqo fju zopecu if PafaVriko eyn noh omj epukuad jifidoet.
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.