Det følgende blogindlæg er, medmindre andet er angivet, skrevet af et medlem af Gamasutras fællesskab.
De tanker og meninger, der kommer til udtryk, er forfatterens og ikke Gamasutras eller dets moderselskab.
Del 1 – Messaging
Del 2 – Memory
Del 3 – Data & Cache
Del 4 – Graphics Libraries
Vi lever i en fantastisk tid at være udviklere i. Med en så stor mængde af fantastiske AAA-grade Engines til rådighed for alle, kan det være lige så nemt som at trække-og-slippe at lave simple spil. Der synes slet ikke at være nogen grund til at skrive en engine længere i disse dage. Og med den almindelige holdning “Write Games, not Engines”, hvorfor skulle man så gøre det?
Denne artikel er primært rettet mod solo-udviklere og små teams. Jeg forudsætter en vis fortrolighed med objektorienteret programmering.
Jeg ønsker at give dig et indblik i at nærme mig Engine-udvikling og vil bruge en simpel fiktiv Engine til at illustrere dette.
Hvorfor skrive en Engine?
Det korte svar er: Lad være, hvis du kan undgå det.
Livet er for kort til at skrive en engine til hvert spil (Taget fra bogen 3D Graphics Programming af Sergei Savchenko)
Det nuværende udvalg af fremragende Engines som Unity, Unreal eller CryEngine er så fleksible, som man kan håbe på, og kan bruges til at lave stort set alle spil. Til mere specialiserede opgaver findes der naturligvis mere specialiserede løsninger som Adventure Game Studio eller RPG Maker, for blot at nævne nogle få. Ikke engang prisen på Engines af kommerciel kvalitet er længere et argument.
Der er kun få nichegrunde tilbage til at skrive din egen engine:
- Du ønsker at lære, hvordan en engine fungerer
- Du har brug for visse funktionaliteter, som ikke er tilgængelige eller de tilgængelige løsninger er ustabile
- Du mener, at du kan gøre det bedre / hurtigere
- Du ønsker at bevare kontrollen over udviklingen
Alle disse er fuldt ud gyldige grunde, og hvis du læser dette, tilhører du sandsynligvis en af disse lejre. Mit mål er ikke at gå ind i en langvarig “Hvilken motor skal jeg bruge?” eller “Skal jeg skrive en motor?” debat her og vil springe direkte ind i det. Så lad os begynde.
Hvordan man fejler ved at skrive en Engine
Venter. Først fortæller jeg dig, at du ikke skal skrive en, derefter forklarer jeg, hvordan man fejler? God introduktion…
Der er i hvert fald mange ting, man skal overveje, før man overhovedet skriver en eneste linje kode. Det første og største problem, som alle, der begynder at skrive en spilmotor, har, kan koges ned til følgende:
Jeg vil se noget gameplay så hurtigt som muligt!
Men jo hurtigere du indser, at det vil tage meget lang tid, før du rent faktisk ser noget interessant ske, jo bedre vil du være stillet ved at skrive din motor.
Tvinge din kode til at vise en form for grafik eller gameplay så hurtigt som muligt, bare for at få en vis visuel bekræftelse af “fremskridt”, er din største fjende på dette punkt. Tag. Din. Tid!
Du skal slet ikke tænke på at begynde med grafikken. Du har sikkert læst en masse OpenGL / DirectX tutorials og bøger og ved, hvordan man renderer en simpel trekant eller sprite. Du tænker måske at et kort kodeuddrag af Rendering af et lille mesh på skærmen er et godt sted at starte. Det er det ikke.
Ja, dine første fremskridt vil være fantastiske. Heck, du kunne løbe rundt på en lille bane i First-Person på bare en dag copy-paster kodestumper fra forskellige tutorials og Stack Overflow. Men jeg garanterer dig, at du vil slette hver eneste linje af den kode 2 dage senere. Endnu værre er det, at du måske endda bliver afskrækket fra at skrive en Engine, da det ikke er motiverende at se mindre og mindre.
Det andet store problem, som udviklere står overfor, når de skriver Engines, er feature creep. Alle vil gerne skrive den hellige gral af Engines. Alle vil have den perfekte Engine, der kan alt. First Person Shooters, taktiske RPG’er, du kan nævne det. Men den simple kendsgerning er, at vi ikke kan det. Endnu. Se bare på de store navne. Ikke engang Unity kan virkelig imødekomme alle spilgenrer perfekt.
Du skal ikke engang tænke på at skrive en Engine, der kan klare mere end én genre i første forsøg. Lad være!
Hvor man egentlig skal begynde, når man skriver en Engine
At skrive en Engine er som at konstruere en rigtig motor til en bil. Trinene er faktisk ret indlysende, forudsat at du ved, hvilket spil (eller bil) du arbejder på. Her er de:
- Sæt præcist fast, hvad din motor skal kunne OG hvad din motor ikke skal kunne.
- Organiser behovene i systemer, som din motor vil kræve.
- Design din perfekte arkitektur, der binder alle disse systemer sammen.
- Gentag trin 1. – 3. så ofte som muligt.
- Kode.
Hvis (= hvis og kun hvis) du bruger nok tid og kræfter på trin 1. – 4. og spildesignet ikke pludselig ændrer sig fra et gyserspil til en spilleautomat (læs: Silent Hill), vil kodning være et meget behageligt forehavende. Kodning vil stadig være langt fra let, men vil være fuldt ud håndterbar, selv for solo-udviklere.
Det er grunden til, at denne artikel hovedsageligt handler om trin 1. – 4. Tænk på trin 5. som “Filling in the Blanks”. 50.000 LOC af tomme felter”.
Den mest afgørende del af alt dette er trin 3. Vi vil fokusere det meste af vores indsats her!
Strin 1. Fastlæg behovene og behovene ikke
Al disse trin kan i første omgang virke ret trivielle. Men det er de i virkeligheden ikke. Du tror måske, at trin 1 i processen med at udvikle en First Person Shooter Engine kan koges ned til dette:
Jeg skal indlæse en bane, spillerens pistol, nogle fjender med AI. Færdig, videre til trin 2.
Hvis det bare var så nemt. Den bedste måde at gå til trin 1 på er at gennemgå hele spillet klik for klik, handling for handling, fra du klikker på ikonet på dit skrivebord, til du trykker på Exit-tasten efter at have rullet Credits. Lav en liste, en stor liste over det, du har brug for. Lav en liste over det, du absolut ikke har brug for.
Dette vil sandsynligvis foregå på denne måde:
Jeg starter spillet, og det går direkte til hovedmenuen. Vil menuen bruge et statisk billede? En Cut Scene? Hvordan styrer jeg hovedmenuen, mus? Keyboard? Hvilken slags GUI Elementer har jeg brug for til Hovedmenuen? Knapper, formularer, rullebjælker? Hvad med musik?
Og det er bare makroovervejelser. Gå ind så detaljeret som muligt. At beslutte, at du har brug for knapper er fint og godt, men overvej også, hvad en knap kan gøre.
Jeg vil have, at knapperne skal have 4 tilstande, op, svæve, ned, deaktiveret. Skal jeg bruge lyd til knapperne? Hvad med specielle effekter? Er de animerede i tomgangstilstand?
Hvis din liste over behov og ikke-behov kun indeholder ca. 10 elementer ved afslutningen af hovedmenuen, har du gjort noget forkert.
På dette stadium simulerer du motoren i din hjerne og skriver ned, hvad der skal gøres. Trin 1 vil blive tydeligere for hver gentagelse, du skal ikke bekymre dig om at overse noget første gang.
Strin 2. Organiser behovene i systemer
Så har du dine lister over de ting, du har brug for og ikke har brug for. Det er tid til at organisere dem. Det er indlysende, at GUI-relaterede ting som knapper vil gå ind i en slags GUI-system. Rendering-relaterede ting går ind i Graphics System / Engine.
Som med trin 1 vil det igen være mere indlysende at beslutte, hvad der skal gå hvorhen, ved din anden gentagelse, efter trin 3. I første gennemløb skal du gruppere dem logisk som i eksemplet ovenfor.
Den bedste reference om “hvad der skal hvorhen” og “hvad der gør hvad” er uden tvivl bogen Game Engine Architecture af Jason Gregory.
Start med at gruppere funktionaliteten. Begynd at tænke på måder at kombinere dem på. Du har ikke brug for Camera->rotateYaw(float yaw)
og Camera->rotatePitch(float pitch)
, hvis du kan kombinere dem i Camera->rotate(float yaw, float pitch)
. Hold det enkelt. For meget funktionalitet (husk, feature creep) vil skade dig senere.
Tænk over, hvilken funktionalitet der skal være offentligt eksponeret, og hvilken funktionalitet der kun skal ligge i selve systemet. Din Renderer skal f.eks. sortere alle gennemsigtige Sprites, før du tegner dem. Funktionen til at sortere disse sprites behøver dog ikke at blive eksponeret. Du ved, at du skal sortere gennemsigtige Sprites, før du tegner, du har ikke brug for et eksternt System til at fortælle dig dette.
Stræk 3. Arkitekturen (Eller, den egentlige artikel)
Vi kunne lige så godt have startet artiklen her. Dette er den interessante og vigtige del.
En af de enkleste mulige arkitekturer, din motor kan have, er at sætte hvert System ind i en Class og lade Main Game Loop kalde deres underrutiner. Det kunne se nogenlunde sådan ud:
while(isRunning)
{
Input->readInput();
isRunning = GameLogic->doLogic();
Camera->update();
World->update();
GUI->update();
AI->update();
Audio->play();
Render->draw();
}
Det virker i første omgang helt fornuftigt. Du har alt det grundlæggende dækket, Input -> behandling af Input -> Output.
Og det vil faktisk være tilstrækkeligt til et simpelt spil. Men det vil være en pine at vedligeholde. Grunden til dette burde være indlysende: Afhængigheder.
Hvert system skal kommunikere med andre systemer på en eller anden måde. Vi har ikke nogen midler til at gøre det i vores ovenstående Game Loop. Derfor viser eksemplet tydeligt, at hvert system skal have en eller anden reference til de andre systemer for at kunne gøre noget fornuftigt. Vores GUI og spillogik skal vide noget om vores input. Vores Renderer skal vide noget om vores Game Logic for at kunne vise noget meningsfuldt.
Dette vil føre til dette arkitektoniske vidunder:
Hvis det lugter af Spaghetti, så er det Spaghetti. Det er helt sikkert ikke det, vi ønsker. Ja, det er nemt og hurtigt at kode. Ja, vi vil få acceptable resultater. Men vedligeholdelsesvenligt er det ikke. Hvis man ændrer et lille stykke kode et sted, kan det have ødelæggende virkninger på alle andre systemer, uden at vi ved det.
Der vil desuden altid være kode, som mange systemer har brug for adgang til. Både GUI og Renderer skal lave Draw calls eller i det mindste have adgang til en eller anden form for Interface til at håndtere dette for os. Ja, vi kunne bare give hvert system mulighed for at kalde OpenGL/DirectX-funktioner direkte, men vi vil ende med en masse redundans.
Vi kunne løse dette ved at samle alle tegnefunktioner i Renderer-systemet og kalde dem fra GUI-systemet. Men så vil Rendering Systemet have specifikke funktioner til GUI’en. Disse har ingen plads i Renderer-systemet og er derfor i strid med trin 1 og 2. Beslutninger, beslutninger.
Det første, vi bør overveje, er derfor at opdele vores Engine i Layers.
Engine Lasagne
Lasagne er bedre end Spaghetti. I hvert fald programmeringsmæssigt. Hvis vi holder os til vores Renderer Eksempel, så er det vi ønsker at kalde OpenGL / DirectX funktioner uden at kalde dem direkte i System. Dette lugter af en Wrapper. Og det er det for det meste også. Vi samler alle tegnefunktionerne i en anden klasse. Disse klasser er endnu mere grundlæggende end vores Systems. Lad os kalde disse nye klasser for Framework.
Tanken bag dette er at abstrahere mange af de lave API-opkald og forme dem til noget, der er skræddersyet til vores spil. Vi ønsker ikke at indstille Vertex Buffer, indstille Index Buffer, indstille Textures, aktivere dette, deaktivere det, bare for at lave et simpelt draw call i vores Renderer System. Lad os placere alle disse ting på lavt niveau i vores Framework. Og jeg kalder bare denne del af Frameworket for “Draw”. Hvorfor? Det eneste den gør er at sætte alt op til at tegne og derefter tegne det. Den er ligeglad med hvad den tegner, hvor den tegner, hvor den tegner, hvorfor den tegner. Det er overladt til Renderer System.
Dette kan virke som en underlig ting, vi vil have hastighed i vores motor, ikke? Flere abstraktionslag = mindre hastighed.
Og du ville have ret, hvis det var 90’erne. Men vi har brug for vedligeholdbarheden og kan leve med det knapt mærkbare hastighedstab for de fleste dele.
Hvordan skal vores Draw Framework så være designet? Simpelthen sagt, som vores egen lille API. SFML er et godt eksempel på dette.
Vigtige ting at huske på:
- Hold det godt dokumenteret. Hvilke funktioner har vi? Hvornår kan de kaldes? Hvordan kaldes de?
- Hold det enkelt. Nemme funktioner som drawMesh(Mesh* oMesh) eller loadShader(String sPath) vil gøre dig glad i det lange løb.
- Hold det funktionelt. Lad være med at være for specifik. i stedet for
drawButtonSprite
skal du have endrawSprite
funktion og lade Caller håndtere resten.
Hvad får vi ud af det? Meget:
- Vi behøver kun at opsætte vores Framework én gang og kan bruge det i alle systemer, vi har brug for (GUI, Renderer….)
- Vi kan nemt ændre de underliggende API’er, hvis vi vælger det, uden at omskrive alle systemer. Skift fra OpenGL til DirectX? Intet problem, bare omskriv Framework-klassen.
- Det holder koden i vores systemer ren og stram.
- Hvis vi har en veldokumenteret grænseflade betyder det, at én person kan arbejde på Frameworket, mens én person arbejder i systemlaget.
Vi vil sandsynligvis ende med noget i stil med dette:
Min tommelfingerregel for, hvad der skal ind i Frameworket, er ret enkel. Hvis jeg har brug for at kalde et eksternt bibliotek (OpenGL, OpenAL, SFML…) eller har datastrukturer/algoritmer som alle systemer har brug for, bør jeg gøre det i Frameworket.
Vi har nu vores første lag lasagne klar. Men vi har stadig denne store kugle af spaghetti over det. Lad os tage fat på det næste.
Messaging
Det store problem er dog stadigvæk. Vores systemer er stadig alle indbyrdes forbundne. Det ønsker vi ikke. Der er et væld af måder at håndtere dette problem på. Events, Messages, Abstract Classes with Function Pointers (Hvor esoterisk) …
Lad os holde os til Messages. Dette er et simpelt koncept, som stadig er meget populært inden for GUI-programmering. Det er også velegnet som et let eksempel for vores Engine.
Det fungerer som et postvæsen. Virksomhed A sender en besked til virksomhed B og beder om, at der skal gøres noget. Disse virksomheder behøver ingen fysisk forbindelse. Virksomhed A går simpelthen ud fra, at virksomhed B vil gøre det på et tidspunkt. Men indtil videre er virksomhed A egentlig ligeglad med, hvornår eller hvordan virksomhed B gør det. Det skal bare gøres. Virksomhed B kan måske endda beslutte at omdirigere meddelelsen til virksomhed C og D og lade dem håndtere den.
Vi kan gå et skridt videre, idet virksomhed A ikke engang behøver at sende den til en bestemt person. Virksomhed A sender blot brevet, og enhver, der føler sig ansvarlig, vil behandle det. På den måde kan virksomhed C og D direkte behandle anmodningen.
Oplysende nok er virksomhederne lig med vores systemer. Lad os tage et kig på et simpelt eksempel:
- Framework meddeler indgangssystemet, at der blev trykket på “A”
- Input oversætter, at tastetryk “A” betyder “Åbn lager” og sender en meddelelse, der indeholder “Åbn lager”
- GUI håndterer beskeden og åbner Inventory-vinduet
- Game Logic håndterer beskeden og sætter spillet på pause
Input er ligeglad med, hvad der bliver gjort med dets besked. GUI er ligeglad med, at Game Logic også behandler den samme meddelelse. Hvis de alle var koblet sammen, ville Input være nødt til at kalde en funktion i GUI-systemet og en funktion i Game Logic. Men det er ikke længere nødvendigt. Det er lykkedes os at afkoble dette ved hjælp af Messages.
Hvordan ser en Message ud? Den skal i det mindste have en eller anden Type. For eksempel kunne åbning af inventar være et eller andet enum kaldet OPEN_INVENTORY
. Dette er tilstrækkeligt for simple Messages som denne. Mere avancerede Meddelelser, der skal indeholde data, skal have en måde at gemme disse data på. Der er et væld af måder at opnå dette på. Den nemmeste at implementere er at bruge en simpel map-struktur.
Men hvordan sender vi Messages? Via en Message Bus selvfølgelig!
Er det ikke smukt? Ikke mere spaghetti, bare god gammel almindelig lasagne. Jeg har med vilje placeret vores spillogik på den anden side af beskedbussen. Som du kan se, har den ingen forbindelse til Framework-laget. Dette er vigtigt for at undgå enhver fristelse til at “bare at kalde den ene funktion”. Tro mig, det vil du før eller senere få lyst til, men det ville ødelægge vores design. Vi har nok systemer, der har med Framework at gøre, det er ikke nødvendigt at gøre det i vores spillogik.
Meddelelsesbussen er en simpel klasse med referencer til alle systemer. Hvis den har en besked i køen, sender beskedbussen den til alle systemer via et simpelt handleMessage(Msg msg)
kald. Til gengæld har hvert system en reference til meddelelsesbussen for at kunne sende meddelelser. Denne kan naturligvis gemmes internt eller overføres som et funktionsargument.
Alle vores Systemer skal derfor arve eller være af følgende form:
class System
{
public:
void handleMessage(Msg *msg);
{
switch(msg->type)
{
//// Example
//case Msg::OPEN_INVENTORY:
// break;
}
}
private:
private:
MessageBus *msgBus;
//// Usage: msgBus->postMessage(msg);
}
(Ja, Ja, rå Pointers…)
Pludselig ændrer vores Game Loop sig til blot at lade Message Bus sende rundt på Messages. Vi vil stadig være nødt til med jævne mellemrum at opdatere hvert System via en eller anden form for update()
-opkald. Men kommunikationen vil blive håndteret på en anden måde.
Som med vores Frameworks skaber brugen af Messages imidlertid overhead. Dette vil gøre motoren en smule langsommere, lad os ikke narre os selv. Men vi er ligeglade! Vi ønsker et rent og simpelt design. En ren og enkel arkitektur!
Og den fedeste del? Vi får fantastiske ting gratis!
Konsollen
Alle meddelelser er stort set et funktionskald. Og hver Message bliver sendt stort set alle steder hen! Hvad hvis vi havde et system, der blot udskriver hver meddelelse, der sendes til et outputvindue? Hvad hvis dette system også kan sende meddelelser, som vi skriver ind i dette vindue?
Ja, vi har lige født en konsol. Og alt, hvad det tog os, er et par linjer kode. Jeg blev helt rundtosset, da jeg første gang så dette i aktion. Den er ikke engang bundet til noget som helst, den eksisterer bare.
En konsol er naturligvis meget nyttig under udviklingen af spillet, og vi kan simpelthen fjerne den i Release, hvis vi ikke ønsker, at spilleren skal have den slags adgang.
In-Game Cinematics, Replays & Debugging
Hvad hvis vi forfalsker Messages? Hvad hvis vi laver et nyt system, der blot sender beskeder på et bestemt tidspunkt? Forestil dig, at det sender noget som MOVE_CAMERA
efterfulgt af ROTATE_OBJECT
.
Og Voila, så har vi In-Game Cinematics.
Hvad nu hvis vi blot optager de Input Messages, der blev sendt i løbet af spillet, og gemmer dem i en fil?
Og Voila, så har vi Replays.
Hvad nu hvis vi bare optager alt, hvad spilleren gør, og når spillet går ned, får dem til at sende disse datafiler til os?
Og Voila, så har vi en nøjagtig kopi af spillernes handlinger, der førte til nedbruddet.
Multi-Threading
Multi-Threading? Ja, Multi-Threading. Vi har afkoblet alle vores systemer. Det betyder, at de kan behandle deres meddelelser, når de vil, hvordan de vil, og vigtigst af alt, hvor de vil. Vi kan lade vores beskedbus bestemme, hvilken tråd hvert system skal behandle en meddelelse -> Multi-Threading
Frame Rate Fixing
Vi har for mange meddelelser til at behandle denne ramme? Intet problem, lad os bare beholde dem i Message Bus Queue og sende dem i den næste frame. Dette vil give os mulighed for at sikre, at vores spil kører med en jævn 60 FPS. Spillerne vil ikke bemærke, at AI’en tager et par Frames længere tid om at “tænke”. De vil dog bemærke, at Frame Rate falder.
Messages er cool.
Det er vigtigt, at vi omhyggeligt dokumenterer hver enkelt Message og dens parametre. Behandl det som et API. Hvis du gør det rigtigt, kan alle udviklere arbejde på forskellige systemer uden at ødelægge noget. Selv hvis et System skulle være offline eller under konstruktion, vil spillet stadig køre og kan testes. Intet lydsystem? Det er fint nok, vi har stadig Visuals. Ingen Renderer, det er fint, vi kan bruge konsollen …
Men meddelelser er ikke perfekte. Desværre.
I nogle tilfælde ønsker vi at kende resultatet af en meddelelse. Nogle gange har vi brug for, at de bliver behandlet med det samme. Vi er nødt til at finde levedygtige muligheder. En løsning på dette er at have en Speedway. Ud over en simpel postMessage
funktion kan vi implementere en postImmediateMessage
funktion, der bliver behandlet med det samme. Håndtering af returmeddelelser er langt nemmere. Disse bliver sendt til vores handleMessage
funktion før eller siden. Vi skal bare huske dette, når vi sender en Message.
Immediate Messages bryder naturligvis Multi-Threading og Frame Rate Fixing, hvis det gøres i overskud. Det er derfor vigtigt at begrænse sig selv for at begrænse deres brug.
Men det største problem med dette system er latenstid. Det er ikke den hurtigste arkitektur. Hvis du arbejder på et First Person Shooter med twitch-lignende responstider, kan dette være en deal breaker.
Tilbage til at designe vores arkitektur
Vi har besluttet at bruge systemer og en beskedbus. Vi ved præcis, hvordan vi ønsker at strukturere vores motor.
Det er tid til trin 4 i vores designproces. Iteration. Nogle funktioner passer måske ikke ind i noget System, vi må finde en løsning. Nogle funktioner skal kaldes i stort omfang og ville tilstoppe Message Bus’en, vi må finde en løsning.
Dette tager tid. Men det er det værd i det lange løb.
Det er endelig tid til at kode!
Stræk 4. Hvor skal man begynde at kode?
Hvor du begynder at kode, skal du læse bogen/artiklen Game Programming Patterns af Robert Nystrom.
Og ellers har jeg skitseret en lille køreplan, som du kan følge. Det er langt fra den bedste måde, men det er produktivt.
- Hvis du vælger en Message Bus type motor, så overvej at kode konsollen og Message Bus først. Når disse er implementeret, kan du fake eksistensen af ethvert System, som endnu ikke er kodet. Du vil have konstant kontrol over hele motoren på alle stadier af udviklingen.
- Overvej at gå videre til GUI’en som det næste, samt den nødvendige Draw-funktionalitet inden for Frameworket. Et solidt GUI parret med konsollen vil give dig mulighed for at fake alle andre systemer endnu nemmere. Testing vil blive en leg.
- Det næste bør være Frameworket, i det mindste dets interface. Funktionaliteten kan følge senere.
- Endeligt skal du gå videre til de andre systemer, herunder Gameplay.
Du vil bemærke, at faktisk rendering af noget, der har med Gameplay at gøre, kan være det sidste, du gør. Og det er en god ting! Det vil føles så meget mere givende og holde dig motiveret til at færdiggøre de sidste detaljer på din Engine.
Din Game Designer vil dog måske skyde dig under denne proces. At teste Gameplay gennem konsolkommandoer er omtrent lige så sjovt som at spille Counter Strike via IRC.
Konklusion
Tag dig god tid til at finde en solid Arkitektur og hold dig til den! Det er det råd jeg håber du tager med dig fra denne artikel. Hvis du gør dette, vil du være i stand til at konstruere en helt fin og vedligeholdelsesvenlig Engine i sidste ende af dagen. Eller århundrede.
Personligt nyder jeg at skrive Engines mere end at lave alt det der Gameplay-arbejde. Hvis du har nogen spørgsmål, er du velkommen til at kontakte mig via Twitter @Spellwrath. Jeg er i øjeblikket ved at færdiggøre endnu en Engine ved hjælp af de metoder, jeg har beskrevet i denne artikel.
Du kan finde del 2 her.