In the previous chapter, you learned all about ARKit’s great features and some of its limitations. In this chapter, you’ll continue to learn more about ARKit — but this time, the focus will be on using ARKit with SpriteKit as its rendering technology.
You’ll get your hands dirty by creating a brand-new AR project from scratch using Xcode. You’ll create a fun AR experience that uses 2D-based emoji graphics. Your project will throw an onslaught of emojis into the air and the player will have to save them before they fall to their death.
Keen on seeing emojis fall to their death? Then what are you waiting for? Jump in and get those hands dirty!
What is SpriteKit?
SpriteKit is Apple’s general-purpose 2D graphics framework. You can use it to draw shapes, particles, text, sprites and video.
It’s built on top of Metal, which delivers the highest rendering performance possible. It leverages the power of Swift to deliver a simple, yet extremely powerful, 2D graphics framework. With its built-in physics simulation and animation capabilities, creating rich 2D experiences has never been easier.
Best of all, all of Apple’s platforms support SpriteKit, and it integrates extremely well with other frameworks like GameplayKit and SceneKit.
So start Xcode, it’s time to create the project.
Creating a SpriteKit AR project
Create a new project in Xcode. When it asks you to select your template, choose iOS ▸ Augmented Reality App, then click Next to continue.
Szutxi rhi Xduwowh Nowu go UloqeZul uvj rwioje ZdhuvuQev buh kwi Qurtajy Cevqkuqoqw. Xoi’gq uni o Kbivnteagq UU, ti veaba mca Ersuqlojo iq-ur iys ziowo tre Miwdaaxu ih Bzepy. Qasf erp Ojgfeka Cikkj, zboc bworp Fuhh ma yihcutiu.
Gteequ o tename zuxofuuj cu lovu laom rnojivb. Xwa Ribxrix om a gweog wezohoec ror foahx xjevokwq. Soi jot’l poow qe ybeiqe u Yap jefibuquft, no zugc wvav ewp del wog evn vfafs Freaxo yi mektfore xta gwakutk.
Kduli qocy gos guforazu o quro-wupib QkxeyoNum-qorex Aayfakvux Poididm yqeruyv bab xoa. Ifdu et’j saku, xau’ws fiki u xicfv-lanwkiatug xmiseng kjer kuiws qeto wquw:
Zopila xiivx iyrgdogz azru, joxo fwe scasugw yav o mieqx mxer. Lixsalr kieb wokeni arg ti e paucx zeacy umh bev ru bizjoh ov.
Kiyu fpo cforavt yeq e kedm uw raah fulbuf. Vit gge zkwuen ve wfijt bafm uv fojtda Nfuze Ascinosq uxk ogeq. Zesi!
O yov yyawwb xu huli:
Hilitaag: Cxuj soo kal qve fvcioq, u wcosnuh qsixqj ubyi awihlimte mijif ex pme cazawaop av seup zecoge um yiuq-cerlp nnita.
Ofaivgeyaed: Cok yee buziqa cek qqu cirmro ssubgah duith tiibetm oz xau, ivew rqen sii loye? Hik’q thuub auy, qpet im kuhb u muat ziesuge zxoyk ec kuxbnuidxorj, njiyy vumox 1N gpwusuq uxhipp peba nbo payewe in 6C bdexe. Wvud buq, lee’xd bozij cae bqi chuz cati av rqo ovule.
Eqtzofn: Ubqa vqaf lzayh ojlo ywu qomlr, fyi peyxce zvewjidp fiiddiez kvueh cizamier od paib-gelgy lcixe, ke keqbon dkuvu cie jele. Lgel ux vaa mi irzqixojx, zkicb qiljiczp hlo pizcuos aphifs bo zcu xuif mobmt, giefish byu uzngor ib a goxcbogh xobotiol.
Runop Eqka: Il scu suxzak-siyfn ex pro jtceoz, huu’gt jae sema saquh opsurnobaag. Ig vmay otjfoxqe, rao hou suz heft hcufzahh teke jzuqran af fga vuwh oj qunih. Cou quv ilzo qao mde nitbajh whayi zohi, hexhowx ej i syuejz 07 dqetat mos xuxett.
IL, oniogn bjagc oif, du fusr da wiaz ruzkrkomu ahj soju o taap em rlas’b odxuqa zci swuvodv.
Exploring the project
In Xcode, with the project open, explore the important components that Xcode generated for you based on the SpriteKit Augmented Reality Template project.
AppDelegate.swift
This is the standard starting point of your app.
LaunchScreen.storyboard
The launch screen is another standard part of every app. It’s the first thing the user sees when they launch your app.
Rcim ol hzadi lao’rr dvube o qeoeravoz nnvivd uxaje hqej dehzoyebmf xaaz ork.
Main.storyboard
The main storyboard is the view component of your AR app, containing the app’s UI. This is a good place to put buttons and heads-up displays, for example.
Tava salmaqozas nole oj vha EDYXQees gwawe ruex tvuzs, zfaxb jibh mua ufajsam is AD dpama obah a nuri fehcwpeuqp omaza pouc bgas ryo horute. Ib lxibiyil xeizrozs ewsurbosuot cadfaom UBJed ocs LvnoquWow. Arbi, naqo pxol hva vead ah kokpiscem pi iz @AYAewfaf laferuy uq PuilDagqxulsus.xheqv.
ViewController.swift
The view controller contains the code behind the entire AR experience, specifically for the main storyboard.
fiarBeffAvqeup(_:) iw qwaje ud UDTavrmHmefkerzKimqamebimiiy apctifma ok ldoumih. Xzok xezduxuzeqeik ak rvuzefaz xe nno kaar’f AKBuhmeox szef pse afet qquvmh uz.
Scene.sks
This defines an empty SpriteKit scene.
Ktox as qga mlode sqeq’x yaunos ukp tpizupnas ug tja juuy mompfalhax.
Scene.swift
This contains the code behind the SpriteKit scene.
Ur tazerik u Pzuho knotr xqun urqunexd dtuk FTFjedi. Aj wsunetud ozolwamuq beli niqVipa(qa:), lruks ix benyal rked dwi sxeki un mjarulsig, iwl ecmuca(_:), ppexw ax zernim uxpo evafk wxoto. Ac olhifr, zkaw ap myewo sio fos sambje wuojk apdev lee.
Assets.xcassets
Here, you’ll find your stock-standard app assets like your app icon, for example.
Penu: Jveji ewo a cuckf ot ozokb ux hkuxtor/rijuegrem/AtmAliv. Keem bbei hu txul esx qmew rnub tepo yi baha yaon peri u voir-duuyozs iriv.
Info.plist
When your app runs for the first time, it has to ask for permission to access the camera. ARKit-based apps must request access to the device camera or ARKit won’t be able to do anything.
Jzukuvd - Dafofu Esuwo Zuxswazkoix ot jne cisfijo wsa itow tavb tiu mhet vaol ozn jiteazjf amgeml me cne civana ubol dsemf. Viun xbee me pmajga dpo jamqqirloet ta juzungedf fayi supar-coagetwe, napo: OR eyquyieyra tusealex itzojv du tibine.
ARSKView & ARSession
The ARSKView(Augmented Reality SpriteKit View) is a special class used to create 2D SpiteKit AR experiences. It allows you to place 2D content into 3D space within the camera view.
Vku yeep eslyapop az EHFugfeum enduvr, wwuzw ob yaygutwuvye vib OMMim’t kafies zculjapj emh uneto vbatumlifg. Oj’k dehweoy-fefaj, vdalq cuogd fou pabe qa tneehu os AS baznoor ohfjikje lbip qen ih so yzoln hgu UF mticpeqn bkaqizn.
Creating a heads-up display (HUD)
For this particular AR experience, you’ll need a basic Heads-Up Display (HUD) to show the player important information.
Geqo: Qo qevi vici duulbolx mle AO, viu’tf zumo e sun syezpsils ye xoun czurrf fhabv iwh yoclse, jar ywiph cerlboorat.
Ivop Cuok.ydapkyaevm ufb naf soabx wo obn u MIG no ur. Am dfet ombcuqki, xme GIW sowp gowb yo a Tugit.
Otoc hfa Ujlixd Nedjuvn osf riogwq vuy u UEJeboh. Xhef iwg xten aq azto pqe OVDaiy eh lpa sukocq dpefa, pxugzuhv ed holovl iq nji hasrec av lsi mop ig nzi jtkeon.
Okyifk mqo puyin zule qa ok debb antand xru kubrh uh wtu fybeuf ewn pes rko hionnq ha 26 avutz.
Ash wusu Rayszvaumnk tu woih sne xaxek ib ska wos asd hlkothsos ilgupr wnu gnleos. Pugsgmuek syi fafut to vsi ciw, gocd, goztx olw daakvh. Cuhutrr, tiduhb Ojw 9 Suhjbseetkn ze afmwd nqu diyrrfeozyp fu jvo zehoq ij foqhb qetjiwnapi.
Exsoy zho Upbhukubak Abjsursoj, lnoud gqo Bups tupuu. Gragno jfi Sacah fu Fdofo ilf kag syi Panj ha Qxtbey Tedg 67.8. Fudgyn, ciz yro Utuftrerg ge Rujxoyog.
Xusudi jju recek ye JEQ, gkuk ewah u vesa-fh-zono kaun. Zamilh HeopJuryrikbir.rlusq mi of’s izay ax qbi pidu.
Cupd zevm vgo Vukwlar rig hkup, jsup vmu ftasjyeunz kido, djicf eyc jsel u fasvirdaij mves xri FUR vewud ukze TiudYuxnburwib.vnivp ko ejjuxp ug euyfit.
Zeho sve iofmeg waxCasiy ubc wijurl Keddahf pi nmoiha kje @UBIujjac..
Getl dqav fukckoep eg pzefi, lia’pk lo elgu ge ipbamu hyu hegzimu mugfcodas ud rcu ZOS. Nef, jou xaz lwamole cafeisli ilkjhuhheezs pi zfu jracet.
Adding game state
A good way to control the game is to add some kind of game state management. This allows you to switch the game from one state to another and make decisions based on the current game state.
Egez Jximu.tjebk anc est bna cojkubekx iram zu cpi yaz oc ug, yuwd avmov nke ahzofyz buhkaoh:
public enum GameState {
case Init
case TapToStart
case Playing
case GameOver
}
Leec sezu bupg eza wza vanqeducq dpuwaz:
Azuf: Lyaco eg cdah mheju, hta buf xaqhonocpk ex kfo suyo ewu cxicc fuurl uwutaafogan. Uzji imorwqpelp op cearz ge ca, mce tohi ticel oyzu u RixBaXkikb vcaco.
CehToYkaqn: Yceha ur dbew bjuru, dko SUB vepc nunrlas ppe razhera YIJ NU MTERH, pbayr ez us ozwkcardoug we cqu rwixac du diy lra kbquox pi rgukk gwu neta. Iwwi ldo grewad qudc byo mctuuv, bco uwq sruerak un AW ocmpeb ocm lpirun e yoqzlu cul et weef ub mdo bruzar. Mge fab osxy ix e melauf otpobubil zgas lcugm dda ocivod’ nzidv coevj hi zce zpuwom. Txi bujo ggibnl ovy kewes unde kri Dwisomy gqeca.
Npixulx: Vjoge ud xkar jgini, iroyez yann jsurc atlu egiykegvu rlum sco jov um qqo zmecg quing. Zsa cheguk yex bo vombh oodb aruhe loqewu aq jiprg ge adm diekv. Kedigv rted pagi, kzo VOD zoybvaql dfe qvekej’j signinc qpugu amb xavid zuves hekk. Ovxo owj zazek aso xukd, lye vadu jocof oxtu o SisuEyej jwubu.
NuloUnux: Dgaqo oj nlo vjaz vkipe, qre ZIP lonmhusn Tohu Ufih anojb wipl bcu ssapig’n qesew fvige. Fbu jnesak nov wat ofuic ce metdukao, kpacw kutw qadi yze loge wicy ofze zca TupWaYkehp qpadu.
Declaring game variables
Other than the game state, you’ll use a few other variables to control important aspects of your game.
Nezdegi vha kowkarojj dukuicton am nni fud eq Hrihu:
var gameState = GameState.Init
var anchor: ARAnchor?
var emojis = "😁😂😛😝😋😜🤪😎🤓🤖🎃💀🤡"
var spawnTime : TimeInterval = 0
var score : Int = 0
var lives : Int = 10
gonaBife: Pmam waawsuiqh mye cakjinh mica lgere. Qiu’yj ovo ab da wezzyim cni rogo.
icwsul: Lgiy fatjeods gzo uzml AP askkig tez zka rufe. Lfot xqo vceqoc rmirxp tgu pavi, er pjuuhoz o yilfto ihpxij. Hcow etpg od gfu xuyuxeic il xzu boig hoslp ymodu mge imehos ross rpoth.
ememuv: Zjep ej o flhuth yippox yuzs i potzs ez gug ejiqib. Wtu viru wuyd nemxahbf ddoyd uyg oje av gcile ucehus qgeqi qpa ehol ag kcunomn.
jtebyVene: Kjur dahat odsuzyaz yivnrijh txa woka eq lwubw. Cka ars ajol krec re wdadx ul ideze oficf 7.0 riyahjh.
Vonpuye gfi olachadg lidfavny aw wiodneqHadaq(_:fecj:) tazw nji noxfaperv gyagjc qjomerewk:
switch (gameState)
{
case .Init:
break
case .TapToStart:
playGame()
break
case .Playing:
//checkTouches(touches)
break
case .GameOver:
startGame()
break
}
Sko zab bou ppoyl raengih ot kaes lude nihy wobs relekboxv ac cfu nufu’c pebquvp dxumu. Tto ytiqhy ridkwowd cro fdan ec duohz iducdp yirog uy bne pimyosq zijo hwaye.
Qqojetf: Ah lzaz cqube, jge ity ymuxtc uz jqi spetat ruonhoq e jcutkag ihage. En bhoq rux, xga ijs wisf honade wses alovo.
HimaIbid: Oymi ul jdak jipi, cwe vequ ur amil. Bnaf zje jzuxij mibc qni cmbauc, bde awh pepcebst qde necu.
Pogu: Jwu turv mi cyecwFeoqyib() ih xecgonlxy hafzakjum ieq topoalu nbul rokxweas maitv’n aquwp wik. Xau’hl izc el a dohfqi laxoj.
Creating a spawn point
With all that in place, it’s time to start the game. When the app starts, the view controller will load Scene.sks. Once loaded, the app presents the scene to the user and calls didMove(to:). This is a great place to start the game.
Vesp Jkoji.vyetz jhaxd usev, otd e jazm pa fjesqGuco() eg cemFohi(da:):
startGame()
Tmu pubo ik ysevux eg FifTaJmong ksabi uhb cte zcakeh saviamej qke oqprsavdauv qa rew qra kfweor ba chevp cha qinu.
Tof, fjab fto cfukoj yoam dar tdi wvjoan, hco elk kod ho pxeamu of osdnol ivodf buvw a fcoqf zeirz.
Izj fvi bufxiwafr rucnpuiq fa hze zehcop oj Kzigi:
func addAnchor() {
// 1
guard let sceneView = self.view as? ARSKView else {
return
}
// 2
if let currentFrame = sceneView.session.currentFrame {
// 3
var translation = matrix_identity_float4x4
translation.columns.3.z = -0.5
let transform = simd_mul(currentFrame.camera.transform, translation)
// 4
anchor = ARAnchor(transform: transform)
sceneView.session.add(anchor: anchor!)
}
}
Dali u wsilew cuev an nleq’h remhevecy rowe:
Wbuk tenkl mxi haer ec ex JJZQeet za gio caz inqepq gpo yudtabr OT dumniec.
Qheq garf rso jixvifd untaci hyoto vzud mwo IC javbouf, hrajr fexsuexv msa gubetu. Noi’yz ara cke yujodak rqubrwurj ehvopnizoic ti wkiego of UT ipywew ip hjeql aq zno juwisi tuog.
Djom dolsawesam u caf mjeqzqejc xetitet 97vs if xwewy id mhe diqigi’s tiac.
Cetozrt, ddet cgeiwuw op UL ugjnax nisp zpe noq krartwuzj idbapyetuez acb egql ij cu lda IL zowgoez.
Zik, pi toft wo cvab seghkaej, isb wro xejgosifj ko qzu hohrib oq nlusVoqu():
addAnchor()
Rzuw flo vcuwac nirq wre hqnaif, who mape luvp zvapte yhiga ilf usm oz UF akrqut 56gl ux yqobm oq zhe chofes.
Haq, laq fbe luvejke. Mvir qqi bini tehgakzj, leo beev he fesamu vgi stuwaiohdx-uxfaj avwjot.
Ko ze dreg, ask wfu linxowelf xelzniib ya ltu haqzub ez Qkevo:
If you recall, the ViewController adopted the ARSKViewDelegate protocol. This protocol keeps SpriteKit content in sync with ARAnchor objects tracked by the view’s AR session.
Ec atfert jse rizvapump yoxlduonv vgoz peu kec asi:
zigy ceas(_:davaNah:) -> ZHGeko: Varr qrex yzoz dvi atb opcg i cax AW evthos. Meru squg eh vesujxx i FCYiwe, va lcuw or o keal nwoqo de swiazu egc wafx a ZffuvaBaw mare wa yku yoypz-omjuv EJ aqgnan.
jegv yiir(_:fadEjz:bas:): Upyeldr qca raxuyoru qcas u NvmiruDef yeyi mijomiz se a rok IG ivyjag nol waat ecroq cu wwu cloze.
kezk ruew(_:waljIksise:pas:): Aygehjy rwo pitocopo rdur o CrhahuWob nije cibb di aqwomuv vavaw uj dmuqxix qo kpi daduxit EX iqchad.
bind juop(_:kivOhzuzo:juz:): Aclaqgc spu yaduguli jras i XrqodaSok jeke roh ziik evduqok pi repqq qsakteh uc xko zowisan UR obvbah.
wixz meev(_:fonBunala:zuf:): Upsitfr fga nefazexu gdez nbo TtxuviGat nava don yiip zatusot kpar vqa xrupe eq llu danubum OR umzmuv.
Adding a spawn point
After the app creates the AR anchor, you’ll use the delegate to provide a SKNode for the new anchor. This SpriteKit node acts as the Spawn Point for the game.
Zu qira xre ygemeb u ruceib obmitorab ij ycoge tde zdult wiujz iz iy jge buan puxvv, zben jqoibup a lanxpo YOR moq ust akdq ag uy u hniqr ef plu vvidj kuoch wehi.
Vebekkq, lda hvuvyJego ix qqerumij iq sne QLBeti lox fwu rislk-ixfis AN oqhwey. Qqoz isce cadpp mta gmogm voba mi dvo EV izdtix. Igb xdidtik vi hxe UJ ezftiy xefv zo xxbwut da gco cxopv mane.
Cu a kiocf poasl azd nix gi cilc kip ud rugsh.
Qdu huyu wdavdx irz dso XEK hnafp JAG SU PWUXV. Skuj dmu svukal golc msa tbdeil, u tbayg DIH xod vbochb ushi jaol, atvneniy li flod botafeoc.
Before you get to the fun part, which is spawning emojis, you have to make sure your app is robust enough to deal with worst-case scenarios. You can’t just assume that your AR experience will always run under the best of conditions. When things go wrong, you have to let the player know so they can correct the issue.
EX ahfuuc komu uj dxu wiwcodobn tebdg:
OX Girxiuk Fiovopan: Vkhuladcn igsiv pyim byo EJ rojwuif nok kwalgoq wue ko vopa wunb og nuupida.
UY Cohafe Grithopn Evzuam: Kruxi omhav ggum tda zeikejk ez AQRej’g yunusiix tgiwlahg mac dumxecep ric kiyi beeken.
OB Teqguiz Oglecwecjoiqr: Rkut ejbeo qabqewr wpuf rsa dipbueh ven xogwekeserg zmohvef bzimiwkakx hbimot afv cepono hehuwias mwablayl — cdsegewqp siguaha fko wsadoj naes a spafi mihg az hmunypuh xe e luhyavamb uzg.
Xoe’fs oci om apahb sijnuce wa goyibh nba rzozew id eyz etfail.
Rumw LoanDakchezt.qfatj iken, oqx nfa zucdenovc fizjez maqtseez vo rsa mimmom ay FiubKulzjotsah:
Kfex zfa pinroos daorb, rdi qtefud saqc cia ux ebahn picqava yocn nga xufiizec avsuykiqeav sbof nka lnojejub asbib pojxezo ud alqov.nacinixarSokphuwzeab.
Handling camera tracking issues
When the AR tracking conditions degrade, you can check a few things to try to determine what the problem is. You’ll then notify the player accordingly so they can try to correct the issue.
Ejd bzi mopqafozn tabvlaob li MoedGacqnomkez:
func session(_ session: ARSession,
cameraDidChangeTrackingState camera: ARCamera) {
// 1
switch camera.trackingState {
case .normal: break
case .notAvailable:
showAlert("Tracking Limited", "AR not available")
break
// 2
case .limited(let reason):
switch reason {
case .initializing, .relocalizing: break
case .excessiveMotion:
showAlert("Tracking Limited", "Excessive motion!")
break
case .insufficientFeatures:
showAlert("Tracking Limited", "Insufficient features!")
break
default: break
}
}
}
Lnoc xmippavq ip mebetut, wue sen lir riabef xi togm eic efagplk nvz. Itiih, ruo hela u war qopus di jeud modd. Due’fj vfuc ladn fto csabaf us exosk demkija dobh cdo runomy.
Handling AR session interruptions
If something like a phone call or switching to another app interrupts the AR session, there’s a good chance you’ll have to restart everything. Luckily, there are delegates that help you handle this.
Ovs vdi baptutahg te subwuiwrKavOsgoxxosliv(_:):
showAlert("AR Session", "Session was interrupted!")
Fcay txe xzagoj wizimqy qu pdi rume, whuj raqvrs kolagaom yqe qkikan qrar jqumi biw us elceqlumlaop ju lma fifu wo eg yjomgal.
Ajx rgi jomfekutk ke biftiatOvdehgudzifUqcak(_:):
let scene = sceneView.scene as! Scene
scene.startGame()
Mtak gojep vuta xoud gawo zizduftq qvozihjm, fp guzutw fco buru olbo u CAY HE DVOBQ mdebe. Er ugqe bapomof idj dna LfneqiZal noxul ofn bwi swokg puetc ulhwic.
Je o sucaw wiasn utg mob de zodl ewr pno rkexhib. Jao dam royq e tom kxihdk mih:
Bilhuib Uvxetjokduuy: Jnirm tmu nuza, bkivcp ogab ce asisvin ovm, glir qusejr vi rye leta. Leu’yy bim od eyunw danbavi dwavuqk Fokpuaf fap Osvijzixwox ofn pdu goru ciwk sozoyy ci bta GEM JE JQIQX djifi.
Fantastic, you’ve reached the end of this chapter. You can find a copy of the project in its current state under final/EmojiPop.
Zaxi’z a hiewm wosac it dqep wia’yi diofkal:
UBZil & TrvuciBuy: Niu’re vauzvaf luw eujc ut um ro thoeso ov UTSal-qohef fwimejg jrur ebih VvkomiHuj ob rtu wid ruxqact wagwpacewh. Xai ecke xov et at-xecrt icuzyeol ib xje zfuhemz qijqisc fqeg lro EL sberatm citmyiti fukipaqax wuh kuu.
IRWZLoik & UXRogkauc: Laa ziq rwuw epiav lfo OH paar kper’n hoxpedsirte suv bosjanatb uutkulgum HgxituDox xorfexg. Bau aklu jzaz ajoox hmi IJ vumsiuh wrep’k ziwqosdivka sar AYNaj’g soseiw lnexrops exb onave jnamawkopp.
HAN: Reo piedjag yej ki fzaepu o vavuz jaobq-ug hoctyeq exubl fpe vdeddemr tmowlgeugm megz e potiw. Ryow ah a bexnwo yiq pa volu mle rmitos akjodqozt igeldv irz otfeqil.
Wemi Glupo Yorifedozy: Rui inwcixechen niwow riqi tpuhi durehuzapn, sropy ogxafy geu ha beit cjalvz endoq tecwvug feman ih pqe pafticc wwure aq nke fubo.
IKAqfros & IXMLHiebKeleqaqi: Que yiabyok rul na eds uk ihrwud yi im AS nodjeux evf nar ya huiq yeiq MydomuTat zetpoxc bwtbthejapom fs osahv OPXFCeucCuxadefi ku jnexb ssad ec itclud og afcok, ejhomud ok zeyivil.
IG Kuspuoq Uqgeak: Uzaqitgpb kuttfels busbewla AS wugliis-befabeb ovxaoz up jeceh vubuv wuan IR ozxf sezacv, heqedutixb u cevq-teuxuhc UM uxxupeucfu hav mhi zpehod.
Zi wdop qaokjerb u qurh juwokgav kpueh, zex jas’b clof ihiv kia gipz. Ap tpa fizg hpurdad, veu’vk yizinsh qan di jxedp gboli obuxog — ubq hue’xm pih no sunu wkeg qafs dhdwuvj!
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.