Shtëpi » Në rritje » Metodat për organizimin e ndërveprimit midis skripteve në Unity3D.

Metodat për organizimin e ndërveprimit midis skripteve në Unity3D.

Unë kam shumë skenarë. Unë dua të jem në gjendje t'i menaxhoj të gjitha në 1 skenar. Unë dua që skripti kryesor të aktivizojë një skript të caktuar dhe më pas kur të përfundojë skripti dytësor ai kthen një vlerë në skriptin kryesor. Pas kësaj, skenari kryesor thërret një skenar tjetër dytësor, etj.

A është atje mënyrën e duhur beje kete?

Pyetje më e saktë:

    A është e mundur të aktivizoni një skript AHK nga një skript tjetër AHK?

    Aktualisht, për të zbuluar që skripti dytësor ka përfunduar, mënyra që unë përdor aktualisht është që menjëherë përpara se skripti dytësor të përfundojë, unë shtyp një kombinim çelësash që do të zbulojë skripti kryesor. Dhe pasi të zbulohet, ai do të rrisë variablin kryesor të skriptit me një dhe kjo do të bëjë që skripti tjetër të aktivizohet. A ka një mënyrë më të mirë për ta arritur këtë?

0

3 pergjigje

Më poshtë është një shembull pune nga dokumentacioni:

; Shembull: Dërgoni një varg të çdo gjatësie nga një skenar në tjetrin. Ky është një shembull pune. ; Për ta përdorur atë, ruani dhe ekzekutoni të dy skriptet e mëposhtme, më pas shtypni Win+Space për të shfaqur një ; InputBox që do t'ju kërkojë të shkruani një varg. ; Ruani skriptin e mëposhtëm si "Receiver.ahk" dhe më pas niseni: #SingleInstance OnMessage(0x4a, "Receive_WM_COPYDATA" ; 0x4a është kthimi i WM_COPYDATA Receive_WM_COPYDATA(wParam, lParam) (StringAddress:= NumGet(lParam + 2*A_PtrSize) ; Merr lpData të anëtarit të CopyDataStruct. me ToolTip kundrejt MsgBox që të mund të kthehemi në kohën e duhur: Këshillë e veglës %A_ScriptName%`nMarrë vargun e mëposhtëm:`n%CopyOfData% kthen true 1 (e vërtetë) është mënyra tradicionale për të pranuar këtë mesazh si ; "Sender.ahk" më pas, shtypni tastin "InputBox", "StringToSend", Futni një tekst për të dërguar: nëse përdoruesi shtyp butonin "Anulo". , TargetScriptTitle) nëse rezultati = Dështon MsgBox SendMessage :`n%TargetScriptTitle% tjetër nëse rezultati = 0 MsgBox Mesazhi u dërgua por dritarja e synuar u përgjigj me 0, që mund të nënkuptojë se e injoroi. kthe Send_WM_COPYDATA(ByRef StringToSend, ByRef TargetScriptTitle) ; ByRef kursen pak memorie në këtë rast. ; Ky funksion dërgon vargun e specifikuar në dritaren e specifikuar dhe kthen përgjigjen. ; Përgjigja është 1 nëse dritarja e synuar e ka përpunuar mesazhin, ose 0 nëse e ka injoruar atë. ( VarSetCapacity(CopyDataStruct, 3*A_PtrSize, 0) ; Vendosni zonën e memories së strukturës. ; Së pari vendosni anëtarin cbData të strukturës në madhësinë e vargut, duke përfshirë terminatorin e tij zero: SizeInBytes:= (StrLen(StringToSend) + 1) * (A_IsUnicode ? 1) NumPut(SizeInBytes, CopyDataStruct, A_PtrSize) ; _TitleMatchMode: = A_TitleMatchMode DetectHiddenWindows Në SetTitleMatchMode 2 SendMessage, 0, &CopyDataStruct, %TargetScriptTitle% ; SetTitleMachTode %Prev_TitleMachTode% ; Njësoj.

në teori mund të përdorni gjithashtu një objekt skedar midis skripteve si metodë stdin/stdout, pasi kur hapni një skedar me një objekt skedari mund ta vendosni atë si të përbashkët.

Ju gjithashtu mund të vendosni një variabël mjedisi dhe t'i kaloni emrin e ndryshores skriptit, duke pasur parasysh se keni një vendosje argumenti në skriptin e synuar, i cili më pas vendos vlerën e ndryshores së mjedisit kur mbyllet. duke përdorur RunWait dhe mund të zbuloni se çfarë kthehet rezultati i skriptit pas ekzekutimit.

Së fundi, shikoni përdorimin e funksionit pasi është ndoshta "praktika më e mirë" për atë që po përpiqeni të bëni. Sepse një funksion mund të bëjë gjithçka që mund të bëjë një skript dhe ju mund t'i kaloni atij një grup për të punuar ose duke përdorur një ByRef në parametrin e grupit. Kjo do të thotë që nuk keni nevojë të shkruani në një grup parametrash kur shkruani një funksion dhe variablat do të çlirojnë memorien pasi funksioni të përfundojë automatikisht. Ju madje mund t'i shkruani funksionet tuaja në një skedar të veçantë dhe të përdorni #Include për t'i përdorur ato në skriptin tuaj.

Funksionet e ngjarjes

Menaxhimi i objekteve të lojës duke përdorur komponentët

Në Redaktorin e Unitetit, ju ndryshoni vetitë e një Komponenti duke përdorur dritaren e Inspektorit. Kështu, për shembull, ndryshimi i pozicionit të komponentit Transform do të ndryshojë pozicionin e objektit të lojës. Po kështu, mund të ndryshoni ngjyrën e materialit të komponentit Renderer ose masën e RigidBody, me efektet përkatëse në shfaqjen ose sjelljen e objektit të lojës. Në pjesën më të madhe, skriptet ndryshojnë gjithashtu vetitë e komponentëve për të menaxhuar objektet e lojës. Dallimi, megjithatë, është se skripti mund të ndryshojë vlerën e pronës gradualisht me kalimin e kohës ose me marrjen e të dhënave nga përdoruesi. Duke ndryshuar, krijuar dhe shkatërruar objekte në një kohë të caktuar, çdo lojë mund të zbatohet.

Qasja në komponentë

Rasti më i thjeshtë dhe më i zakonshëm është kur skripti duhet të aksesojë komponentë të tjerë të bashkangjitur në të njëjtin GameObject. Siç u përmend në seksionin e hyrjes, një komponent është në fakt një shembull i një klase, kështu që hapi i parë është të merrni një referencë për shembullin e komponentit me të cilin dëshironi të punoni. Kjo bëhet duke përdorur funksionin GetComponent. Në mënyrë tipike, një objekt përbërës ruhet në një variabël, kjo bëhet në C# duke përdorur sintaksën e mëposhtme:

(); }

Në UnityScript sintaksa është paksa e ndryshme:

Funksioni Start() (var rb = GetComponent. (); }

Void Start() (Rigidbody rb = GetComponent ();

// Ndrysho masën e trupit të ngurtë të objektit rb.mass = 10f;

Void Start() (Rigidbody rb = GetComponent Një veçori shtesë që nuk disponohet në dritaren e Inspektorit është thirrja e funksioneve të një shembulli përbërës:

();

//Shto një forcë në Trupin e ngurtë.

rb.AddForce(Vector3.up * 10f); )

Mbani në mend se nuk ka asnjë arsye pse nuk mund të keni më shumë se një skript të personalizuar të bashkangjitur në të njëjtin objekt. Nëse keni nevojë të përdorni një skript nga një tjetër, mund të përdorni GetComponent si zakonisht, duke përdorur emrin e klasës së skriptit (ose emrin e skedarit) për të specifikuar se çfarë lloji të Komponentit ju nevojitet.

Lidhja e objekteve përmes variablave

Mënyra më e lehtë për të gjetur objektin e dëshiruar të lojës është të shtoni një variabël të llojit GameObject me nivel aksesi publik në skript:

Armiku i klasës publike: MonoSjellja (lojtar publik i GameObject; // Variabla dhe funksione të tjera... )

Ndryshorja do të jetë e dukshme në dritaren e Inspektorit si çdo tjetër:

Tani mund të tërhiqni një objekt nga skena ose paneli i Hierarkisë në këtë variabël për ta caktuar atë. Funksioni GetComponent dhe qasja në variablat e komponentit janë të disponueshme si për këtë objekt ashtu edhe për të tjerët, që do të thotë se mund të përdorni kodin e mëposhtëm:

Klasa publike Armiku: MonoSjellja (lojtar publik i GameObject; void Start() ( // Filloni armikun dhjetë njësi pas karakterit të lojtarit. transform.position = player.transform.position - Vector3.forward * 10f; ) )

Për më tepër, nëse deklaroni një variabël me akses publik dhe një lloj komponenti të caktuar në skriptin tuaj, mund të tërhiqni çdo objekt që ka atë lloj komponenti të bashkangjitur. Kjo do të lejojë që komponenti të aksesohet drejtpërdrejt dhe jo përmes një objekti të lojës.

Player Transform Publik Transform;

Lidhja e objekteve përmes variablave është më e dobishme kur keni të bëni me objekte individuale që kanë një marrëdhënie të qëndrueshme. Mund të përdorni një grup për të ruajtur shoqatat me objekte të shumta të të njëjtit lloj, por shoqatat duhet të vendosen ende në redaktuesin Unity dhe jo në kohën e ekzekutimit. Shpesh është i përshtatshëm për të gjetur objekte në kohën e ekzekutimit, dhe Unity ofron dy mënyra kryesore për ta bërë këtë, të përshkruara më poshtë.

Gjetja e objekteve të fëmijëve

Ndonjëherë skena e lojës mund të përdorë objekte të shumta të të njëjtit lloj, siç janë armiqtë, pikat e rrugës dhe pengesat. Mund të jetë e nevojshme t'i gjurmoni ato në një skript specifik që i menaxhon ose reagon ndaj tyre (për shembull, të gjitha pikat e rrugës mund të kërkohen nga një skript për gjetjen e shtigjeve). Është e mundur të përdoren variabla për të lidhur këto objekte, por do ta bëjë procesin e projektimit të lodhshëm nëse çdo pikë e re duhet të tërhiqet në një variabël në skript. Po kështu, nëse fshini një pikë, do të duhet të fshini referencën për veçorinë që mungon. Në raste të tilla, shpesh është e përshtatshme të menaxhosh një grup objektesh duke i bërë ata fëmijë të një objekti të një prindi të vetëm. Objektet e fëmijëve mund të merren duke përdorur komponentin Transform të prindit (pasi të gjitha objektet e lojës përmbajnë në mënyrë implicite një Transformim):

Përdorimi i UnityEngine; Klasa publike WaypointManager: MonoSjellja (Transformimi publik i pikave të rrugës; void Start() (pikat e rrugës = transformimi i ri; int i = 0; foreach (Transformoni t në transformim) (pikat udhëzuese = t;)) )

Ju gjithashtu mund të gjeni një objekt të caktuar fëmijë me emër duke përdorur funksionin

JavaScript është projektuar në një paradigmë të thjeshtë të bazuar në objekte. Një objekt është një koleksion pronash, dhe një pronë është një lidhje midis një emri (ose kyç) dhe një vlerë. Vlera e një vetie mund të jetë një funksion, në të cilin rast vetia njihet si metodë. Përveç objekteve që janë të paracaktuara në shfletues, ju mund të përcaktoni objektet tuaja. Ky kapitull përshkruan se si të përdorni objektet, vetitë, funksionet , dhe metodat, dhe si të krijoni objektet tuaja.

Pasqyrë e objekteve

Objektet në JavaScript, ashtu si në shumë gjuhë të tjera programimi, mund të krahasohen me objektet në jetën reale. Koncepti i objekteve në JavaScript mund të kuptohet me jetën reale, objekte të prekshme.

Në JavaScript, një objekt është një entitet i pavarur, me veti dhe lloj. Krahasoni atë me një filxhan, për shembull. Një filxhan është një objekt, me veti. Një filxhan ka një ngjyrë, një dizajn, peshë, një material nga i cili është bërë, etj. Në të njëjtën mënyrë, objektet JavaScript mund të kenë veti, të cilat përcaktojnë karakteristikat e tyre.

Objektet dhe vetitë

Një objekt JavaScript ka veti të lidhura me të. Një veti e një objekti mund të shpjegohet si një ndryshore që i bashkëngjitet objektit. Karakteristikat e objektit janë në thelb e njëjta gjë si variabla të zakonshëm JavaScript, me përjashtim të lidhjes me objektet. Vetitë e një objekti përcaktojnë karakteristikat e objektit. Ju aksesoni vetitë e një objekti me një shënim të thjeshtë me pika:

Emri i objektit.Emri i pronës

Ashtu si të gjitha variablat JavaScript, si emri i objektit (i cili mund të jetë një variabël normal) ashtu edhe emri i pronës janë të ndjeshëm ndaj shkronjave të vogla. Ju mund të përcaktoni një pronë duke i caktuar një vlerë. Për shembull, le të krijojmë një objekt të quajtur myCar dhe t'i japim vetitë e quajtur make , model , dhe viti si më poshtë:

Var myCar = Objekti i ri(); myCar.make = "Ford"; myCar.model = "Mustang"; myCar.vit = 1969;

myCar.ngjyra; // e papërcaktuar Vetitë e objekteve JavaScript gjithashtu mund të aksesohen ose vendosen duke përdorur një shënim kllapash (për më shumë detaje shihni aksesorët e vetive). Objektet nganjëherë quhen vargjeve asociative

MyCar["make"] = "Ford"; myCar["model"] = "Mustang"; myCar["viti"] = 1969;

Një emër i pronës së objektit mund të jetë çdo varg i vlefshëm JavaScript, ose çdo gjë që mund të konvertohet në një varg, duke përfshirë vargun bosh. Megjithatë, çdo emër pronësie që nuk është një identifikues i vlefshëm JavaScript (për shembull, një emër pronësie që ka një hapësirë ​​ose një vizë ndarëse, ose që fillon me një numër) mund të aksesohet vetëm duke përdorur shënimin e kllapave katrore. Ky shënim është gjithashtu shumë i dobishëm kur emrat e pronave duhet të përcaktohen në mënyrë dinamike (kur emri i pronës nuk përcaktohet deri në kohën e ekzekutimit). Shembujt janë si më poshtë:

// katër variabla krijohen dhe caktohen në një lëvizje të vetme, // të ndara me presje var myObj = New Object(), str = "myString", rand = Math.random(), obj = new Object(); myObj.type = "Sintaksa e pikave"; myObj["data e krijuar"] = "String me hapësirë"; myObj = "Vlera e vargut"; myObj = "Numër i rastësishëm"; myObj = "Objekt"; myObj[""] = "Edhe një varg bosh"; konsol.log(myObj);

Ju lutemi vini re se të gjithë çelësat në shënimin e kllapave katrore konvertohen në varg, përveç nëse janë "Simbole", pasi emrat e vetive të objekteve JavaScript (çelësat) mund të jenë vetëm vargje ose simbole (në një moment, emrat privatë do të shtohen gjithashtu si propozimi i fushave të klasës përparon, por ju nuk do ta bëni përdorni m me formë). Për shembull, në kodin e mësipërm, kur çelësi obj shtohet në myObj, JavaScript do të telefononi metodën obj.toString() dhe përdorni këtë varg rezultati si çelësin e ri.

Ju gjithashtu mund të përdorni vetitë duke përdorur një vlerë vargu që ruhet në një variabël:

Var emri i pronës = "make"; myCar = "Ford"; Emri i pronës = "model"; myCar = "Mustang";

Përdorimi i një funksioni konstruktori

Përndryshe, mund të krijoni një objekt me këto dy hapa:

  1. Përcaktoni llojin e objektit duke shkruar një funksion konstruktor. Ekziston një konventë e fortë, me arsye të mirë, për të përdorur një shkronjë të madhe fillestare.
  2. Krijo një shembull të objektit me new .

Për të përcaktuar një lloj objekti, krijoni një funksion për llojin e objektit që specifikon emrin, vetitë dhe metodat e tij. Për shembull, supozoni se dëshironi të krijoni një lloj objekti për makinat. Ju dëshironi që ky lloj objekti të quhet Makinë, dhe ju duan që ajo të ketë veti për prodhimin, modelin dhe vitin. Për ta bërë këtë, duhet të shkruani funksionin e mëposhtëm:

Funksioni Makinë (make, model, vit) ( this.make = make; this.model = model; this.year = vit; )

Vini re përdorimin e kësaj për të caktuar vlera në vetitë e objektit bazuar në vlerat e kaluara në funksion.

Tani mund të krijoni një objekt të quajtur mycar si më poshtë:

Var mycar = Makinë e re ("Shqiponja", "Talon TSi", 1993);

Kjo deklaratë krijon mycar dhe i cakton vlerat e specifikuara për vetitë e saj. Atëherë vlera e mycar.make është vargu "Shqiponja", mycar.year është numri i plotë 1993, e kështu me radhë.

Ju mund të krijoni çdo numër të objekteve të makinës duke thirrur në të reja. Për shembull,

Var kenscar = Makinë e re ("Nissan", "300ZX", 1992); var vpgscar = Makina e re ("Mazda", "Miata", 1990);

Një objekt mund të ketë një veti që është në vetvete një objekt tjetër. Për shembull, supozoni se përcaktoni një objekt të quajtur person si më poshtë:

Funksioni Personi (emri, mosha, seksi) ( this.em = emri; this.age = mosha; this. sex = seksi; )

dhe më pas instantoni dy objekte të reja të personit si më poshtë:

Var rand = Personi i ri ("Rand McKinnon", 33, "M"); var ken = Personi i ri ("Ken Jones", 39, "M");

Më pas, mund të rishkruani përkufizimin e Makinës për të përfshirë një pronë pronari që merr objektin e një personi, si më poshtë:

Funksioni Makinë (marrë, model, vit, pronar) ( this.make = make; this.model = model; this.year = vit; this.pronar = pronar; )

Për të instancuar objektet e reja, më pas përdorni sa vijon:

Var car1 = Makinë e re ("Shqiponja", "Talon TSi", 1993, rand); var car2 = Makinë e re ("Nissan", "300ZX", 1992, ken);

Vini re se në vend që të kalojnë një varg literal ose vlerë të plotë gjatë krijimit të objekteve të reja, deklaratat e mësipërme kalojnë objektet rand dhe ken si argumente për pronarët. Pastaj nëse doni të zbuloni emrin e pronarit të makinës2, mund të përdorni pronën e mëposhtme:

Makina2.pronari.emri

Vini re se gjithmonë mund të shtoni një pronë në një objekt të përcaktuar më parë. Për shembull, deklarata

Car1.ngjyrë = "e zezë";

i shton një ngjyrë të vetive makinës1 dhe i cakton asaj vlerën "e zezë". Megjithatë, kjo nuk prek asnjë objekt tjetër. Për të shtuar pronën e re në të gjitha objektet e të njëjtit lloj, duhet të shtoni pronën në përkufizimin e llojit të objektit Car.

Duke përdorur metodën Object.create

Shihni gjithashtu

  • Për t'u zhytur më thellë, lexoni për detajet e modelit të objekteve të javaScript.
  • Për të mësuar rreth klasave ECMAScript 2015 (një mënyrë e re për të krijuar objekte), lexoni kapitullin e klasave JavaScript.
Edhe një projekt mesatar Unity3D shumë shpejt mbushet me një numër të madh skriptesh të ndryshme dhe lind pyetja se si këto skripta ndërveprojnë me njëri-tjetrin.
Ky artikull ofron disa qasje të ndryshme për organizimin e ndërveprimeve të tilla, nga të thjeshta në ato të avancuara, dhe përshkruan se çfarë problemesh mund të çojë secila qasje, si dhe sugjeron mënyra për të zgjidhur këto probleme.

Qasja 1. Detyra nëpërmjet redaktorit Unity3D

Le të themi se kemi dy skenarë në projektin tonë. E para është përgjegjëse për shënimin e pikëve në lojë, dhe e dyta është për ndërfaqen e përdoruesit, e cila shfaq numrin e pikëve të shënuara në ekranin e lojës.
Le t'i quajmë menaxherë të dy skripteve: ScoresManager dhe HUDManager.
Si mundet menaxheri përgjegjës për menunë e ekranit të marrë numrin aktual të pikëve nga menaxheri përgjegjës për dhënien e pikëve?
Supozohet se në Hierarkinë e objekteve të skenës ekzistojnë dy objekte, njërit prej të cilëve i është caktuar skripti ScoresManager dhe tjetrit skripti HUDManager.
Një nga qasjet përmban parimin e mëposhtëm:
Në skriptin UIManager ne përcaktojmë një variabël të tipit ScoresManager:

Klasa publike HUDManager: MonoSjellja ( publike ScoresManager ScoresManager; )
Por ndryshorja ScoresManager gjithashtu duhet të inicializohet me një shembull të klasës. Për ta bërë këtë, zgjidhni objektin në hierarkinë e objektit të cilit i është caktuar skripti HUDManager dhe në cilësimet e objektit do të shohim ndryshoren ScoresManager me vlerën None.

Pas së cilës, ne kemi mundësinë të hyjmë në skriptin ScoresManager nga kodi HUDManager, kështu:

Klasa publike HUDManager: MonoSjellje ( publik ScoresManager ScoresManager; Përditësim publik i zbrazët () ( ShowScores (ScoresManager.Scores); ) )
Është e thjeshtë, por loja nuk kufizohet vetëm në pikët e shënuara, HUD mund të shfaqë jetën aktuale të lojtarit, një meny të veprimeve të disponueshme të lojtarëve, informacione për nivelin dhe shumë më tepër. Një lojë mund të përmbajë dhjetëra ose qindra skripta të ndryshëm që duhet të marrin informacion nga njëri-tjetri.
Për të marrë të dhëna nga një skript tjetër në një skript, çdo herë do të na duhet të përshkruajmë një variabël në një skript dhe ta caktojmë (zvarritni dhe lëshoni manualisht) duke përdorur redaktuesin, që në vetvete është një punë e lodhshme që mund ta harroni lehtësisht ta bëni dhe pastaj kaloni një kohë të gjatë duke kërkuar se cili nga variablat nuk është inicializuar.
Nëse duam të rifaktorojmë diçka, riemërtojmë skriptin, atëherë të gjitha inicializimet e vjetra në hierarkinë e objekteve të lidhura me skriptin e riemërtuar do të rivendosen dhe do të duhet t'i caktojmë përsëri.
Në të njëjtën kohë, një mekanizëm i tillë nuk funksionon për parafabrikat - krijimi dinamik i objekteve nga një shabllon. Nëse ndonjë prefab ka nevojë të aksesojë një menaxher të vendosur në hierarkinë e objektit, atëherë nuk do të jeni në gjendje të caktoni një element nga hierarkia tek vetë parafabrika, por do të duhet së pari të krijoni një objekt nga parafabrika dhe më pas të caktoni në mënyrë programore një shembull të menaxheri në një variabël të objektit të sapokrijuar. Punë e panevojshme, kod i panevojshëm, lidhje shtesë.
Qasja e mëposhtme zgjidh të gjitha këto probleme.

Qasja 2. Beqarët

Le të aplikojmë një klasifikim të thjeshtuar të skripteve të mundshme që përdoren gjatë krijimit të një loje. Lloji i parë i skripteve: "skriptet e menaxherëve", i dyti: "skriptet e objekteve të lojës".
Dallimi kryesor midis njërës dhe tjetrës është se "skriptet e menaxherit" kanë gjithmonë një shembull të vetëm në lojë, ndërsa "skriptet e objekteve të lojës" mund të kenë më shumë se një shembull.

Shembuj

Si rregull, në një kopje të vetme ka skripta përgjegjës për të logjika e përgjithshme ndërfaqja e përdoruesit, për të luajtur muzikë, për gjurmimin e kushteve të përfundimit të nivelit, për menaxhimin e sistemit të detyrave, për shfaqjen e efekteve speciale, etj.
Në të njëjtën kohë, skriptet e objekteve të lojës ekzistojnë në një numër të madh rastesh: çdo zog nga "Angry Birds" kontrollohet nga një shembull i skenarit të zogut me gjendjen e tij unike; për çdo njësi në strategji, krijohet një shembull i një skenari njësi që përmban numrin aktual të jetëve, pozicionin në fushë dhe qëllimin personal; Sjellja e pesë ikonave të ndryshme sigurohet nga instanca të ndryshme të skripteve të njëjta përgjegjëse për këtë sjellje.
Në shembullin nga hapi i mëparshëm, skriptet HUDManager dhe ScoresManager ekzistojnë gjithmonë në një kopje të vetme. Për të bashkëvepruar me njëri-tjetrin, ne përdorim modelin "Singleton".
Në klasën ScoresManager ne do të përshkruajmë një veti statike të tipit ScoresManager, e cila do të ruajë një shembull të vetëm të menaxherit të rezultateve:

Klasa publike ScoresManager: MonoSjellja (Shembulli publik statik ScoresManager (merr; grup privat; ) Rezultatet publike int; )
Mbetet të inicializohet vetia Instance me një shembull të klasës që krijon mjedisi Unity3D. Meqenëse ScoresManager është pasardhësi i MonoBehaviour, ai merr pjesë në cikli jetësor të gjithë skriptet aktive në skenë dhe gjatë inicializimit të skenarit, thirret metoda Awake. Në këtë metodë vendosim kodin për inicializimin e vetive Instance:

Klasa publike ScoresManager: MonoSjellje (Shembulli publik statik ScoresManager (merr; grup privat; ) rezultatet publike int; publik void Awake() ( Instance = this; ) )
Pas kësaj, mund të përdorni ScoresManager nga skriptet e tjera si më poshtë:

Klasa publike HUDManager: MonoSjellje (Përditësim publik i zbrazët () ( ShowScores (ScoresManager.Instance.Scores); ) )
Tani nuk ka nevojë që HUDManager të përshkruajë një fushë të tipit ScoresManager dhe ta caktojë atë në redaktuesin Unity3D, çdo "menaxher i skriptit" mund të sigurojë qasje në vetvete përmes veçorisë Statike Instance, e cila do të inicializohet në funksionin Awake;

Pro

- nuk ka nevojë të përshkruani fushën e skriptit dhe ta caktoni atë përmes redaktorit Unity3D.
- ju mund të rifaktoni me siguri kodin, nëse diçka bie, përpiluesi do t'ju njoftojë.
- "Skriptet e tjera të menaxherit" tani mund të aksesohen nga parafabrikat, përmes veçorisë Instance.

Kundër

- qasja siguron qasje vetëm në "skriptet e menaxherit" që ekzistojnë në një kopje të vetme.
- lidhje e fortë.
Le të shohim "minusin" e fundit në më shumë detaje.
Le të zhvillojmë një lojë në të cilën ka personazhe (njësi) dhe këta personazhe mund të vdesin (vdesin).
Diku ka një copë kodi që kontrollon nëse personazhi ynë ka vdekur:

Njësia e klasës publike: MonoSjellja ( publike int LifePoints; publik void TakeDamage(int dëm) ( LifePoints - = dëm; nëse (LifePoints<= 0) Die(); } }
Si mund të reagojë loja ndaj vdekjes së një personazhi? Shumë reagime të ndryshme! Këtu janë disa opsione:
- duhet të hiqni personazhin nga skena e lojës në mënyrë që ai të mos shfaqet më në të.
- loja jep pikë për çdo personazh të vdekur, ju duhet t'i shtoni ato dhe të përditësoni vlerën në ekran.
- një panel i veçantë shfaq të gjithë personazhet në lojë, ku mund të zgjedhim një personazh specifik. Kur një personazh vdes, ne duhet të përditësojmë panelin, ose ta heqim personazhin prej tij ose të shfaqim se ai ka vdekur.
- duhet të luani efektin zanor të vdekjes së personazhit.
- duhet të luani efektin vizual të vdekjes së personazhit (shpërthim, spërkatje gjaku).
- Sistemi i arritjeve të lojës ka një arritje që numëron numrin total të personazheve të vrarë gjatë gjithë kohës. Ju duhet të shtoni personazhin që sapo vdiq në banak.
- sistemi i analitikës së lojës dërgon faktin e vdekjes së personazhit në një server të jashtëm, ky fakt është i rëndësishëm për ne për të gjurmuar përparimin e lojtarit;
Duke pasur parasysh të gjitha sa më sipër, funksioni Die mund të duket kështu:

Void privat vdes () (fshijFromscene (); scoresmanager.instance.onunitdied (kjo); niveliConditionManager.instance.onunitdied (kjo); njësi AchievementsManager.Instance.OnUnitDied(this);
Rezulton se pas vdekjes së tij personazhi duhet t'ua dërgojë këtë fakt të trishtë të gjithë komponentëve që janë të interesuar për të, ai duhet të dijë për ekzistencën e këtyre komponentëve dhe duhet të dijë se ata janë të interesuar për të. A nuk ka shumë njohuri për një njësi të vogël?
Meqenëse loja, logjikisht, është një strukturë shumë e lidhur, ngjarjet që ndodhin në komponentë të tjerë janë me interes për të tjerët njësia këtu;
Shembuj të ngjarjeve të tilla (jo të gjitha):
- Kushti për kalimin e nivelit varet nga numri i pikëve të fituara nëse shënoni 1000 pikë, ju e kaloni nivelin (LevelConditionManager është i lidhur me ScoresManager).
- Kur marrim 500 pikë, arrijmë një fazë të rëndësishme të kalimit të nivelit, duhet të luajmë një melodi qesharake dhe një efekt vizual (ScoresManager është i lidhur me EffectsManager dhe SoundsManager).
- Kur personazhi rimëkëmbet shëndetin, duhet të luani efektin shërues mbi figurën e personazhit në panelin e karaktereve (UnitsPanel është i lidhur me EffectsManager).
- dhe kështu me radhë.
Si rezultat i lidhjeve të tilla, arrijmë në një foto të ngjashme me atë në vijim, ku të gjithë dinë gjithçka për të gjithë:

Shembulli i vdekjes së personazheve është paksa i ekzagjeruar, raportimi i një vdekjeje (ose ngjarje tjetër) për gjashtë komponentë të ndryshëm nuk është shumë i zakonshëm. Por opsionet kur, gjatë ndonjë ngjarjeje në lojë, funksioni në të cilin ndodhi ngjarja informon 2-3 komponentë të tjerë për të, gjenden në të gjithë kodin.
Qasja e mëposhtme përpiqet të zgjidhë këtë problem.

Qasja 3. Eteri global (Agregatori i ngjarjeve)

Le të prezantojmë një komponent të veçantë "EventAggregator", funksioni kryesor i të cilit është të ruajë një listë të ngjarjeve që ndodhin në lojë.
Një ngjarje në lojë është një funksionalitet që i ofron çdo komponenti tjetër mundësinë për t'u abonuar në vetvete dhe për të publikuar faktin që kjo ngjarje ka ndodhur. Zbatimi i funksionalitetit të ngjarjes mund të jetë çdo gjë që zhvilluesi dëshiron, ju mund të përdorni zgjidhje standarde gjuhësore ose të shkruani zbatimin tuaj;
Një shembull i zbatimit të një ngjarjeje të thjeshtë nga shembulli i mëparshëm (për vdekjen e një njësie):

Klasa publike UnitDiedEvent ( Lista private vetëm për lexim > _Callbacks = Lista e re >(); publik void Subscribe (Veprim kthimi i thirrjeve) ( _callbacks.Add(callback); ) public void Publish(njësia e njësisë) ( foreach (Veprim kthimi i thirrjes në _kthimet e thirrjes) kthimi i thirrjes(njësia); ) )
Shtoni këtë ngjarje në "EventAggregator":

Ngjarjet e klasës publike (Ngjarja statike publike UnitDiedEvent UnitDied;)
Tani, funksioni Die nga shembulli i mëparshëm me tetë rreshta kodi është konvertuar në një funksion me një rresht kodi. Ne nuk kemi nevojë të raportojmë se një njësi ka vdekur për të gjithë komponentët e interesuar dhe të dimë për të interesuarit. Ne thjesht publikojmë faktin e ngjarjes:

Private void Die() ( EventAggregator.UnitDied.Publish(this);)
Dhe çdo komponent që është i interesuar për këtë ngjarje mund të reagojë ndaj tij si më poshtë (duke përdorur shembullin e një menaxheri përgjegjës për numrin e pikëve të shënuara):

Klasa publike ScoresManager: MonoSjellja ( rezultatet publike int; publik void Awake() ( EventAggregator.UnitDied.Subscribe(OnUnitDied); ) void privat OnUnitDied(njësia e njësisë) ( Notat += CalculateScores(njësia); ))
Në funksionin Zgjohuni, menaxheri pajtohet në një ngjarje dhe kalon një delegat përgjegjës për trajtimin e asaj ngjarje. Vetë mbajtësi i ngjarjeve merr një shembull të një njësie të vdekur si parametër dhe shton numrin e pikëve në varësi të llojit të kësaj njësie.
Në të njëjtën mënyrë, të gjithë përbërësit e tjerë që janë të interesuar për ngjarjen e vdekjes së një njësie mund të abonohen në të dhe ta përpunojnë atë kur ndodh ngjarja.
Si rezultat, një diagram i lidhjeve midis komponentëve ku secili komponent dinte për njëri-tjetrin kthehet në një diagram ku komponentët dinë vetëm për ngjarjet që ndodhin në lojë (vetëm ngjarjet që u interesojnë), por nuk u intereson se ku këto ngjarje kanë ardhur nga. Diagrami i ri do të duket kështu:

Më pëlqen një interpretim tjetër: imagjinoni që drejtkëndëshi "EventAggregator" shtrihej në të gjitha drejtimet dhe kapte të gjithë drejtkëndëshat e tjerë brenda vetes, duke u kthyer në kufijtë e botës. Në kokën time, "EventAggregator" mungon plotësisht në këtë diagram. “EventAggregator” është thjesht bota e lojës, një lloj “game ether”, ku pjesë të ndryshme të lojës thërrasin “Hej, njerëz! Njësia filani ka vdekur!”, dhe të gjithë e dëgjojnë transmetimin dhe nëse ndonjë nga ngjarjet që dëgjojnë u intereson, do të reagojnë për të. Kështu, nuk ka lidhje, secili komponent është i pavarur.
Nëse unë jam një komponent dhe jam përgjegjës për publikimin e një ngjarjeje, atëherë bërtas në ajër se ky vdiq, ky mori një nivel, një predhë u përplas në një tank. Dhe nuk më intereson nëse dikush kujdeset për këtë. Ndoshta askush nuk po e dëgjon këtë ngjarje tani, ose ndoshta njëqind objekte të tjera janë abonuar në të. Si autor i ngjarjes, kjo nuk më shqetëson aspak, nuk di asgjë për ta dhe nuk dua ta di.
Kjo qasje e bën të lehtë prezantimin e funksionalitetit të ri pa ndryshuar atë të vjetër. Le të themi se vendosëm të shtojmë një sistem arritjesh në lojën e përfunduar. Ne po krijojmë një komponent të ri të sistemit të arritjeve dhe po abonojmë të gjitha ngjarjet që na interesojnë. Asnjë kod tjetër nuk është ndryshuar. Nuk ka nevojë të kalosh nëpër komponentë të tjerë dhe të thërrasësh sistemin e arritjeve prej tyre dhe t'i thuash që të lutem numëro ngjarjen time. Për më tepër, të gjithë ata që publikojnë ngjarje në botë nuk dinë asgjë për sistemin e arritjeve, madje as për faktin e ekzistencës së tij.

Komentoni

Kur them se asnjë kod tjetër nuk ndryshon, sigurisht që jam pak i pa sinqertë. Mund të rezultojë se sistemi i arritjeve është i interesuar për ngjarje që thjesht nuk ishin publikuar në lojë më parë, sepse ata nuk ishin të interesuar për ndonjë sistem tjetër më parë. Dhe në këtë rast, ne do të duhet të vendosim se çfarë ngjarje të reja do të shtojmë në lojë dhe kush do t'i publikojë ato. Por në një lojë ideale, të gjitha ngjarjet e mundshme tashmë ekzistojnë dhe valët e ajrit janë të mbushura në maksimum me to.

Pro

- jo komponentë të lidhur, thjesht duhet të publikoj ngjarjen, dhe kush është i interesuar për të nuk ka rëndësi.
- nuk ka lidhje midis komponentëve, thjesht pajtohem në ngjarjet që më nevojiten.
- mund të shtoni module individuale pa ndryshuar funksionalitetin ekzistues.

Kundër

- duhet të përshkruani vazhdimisht ngjarje të reja dhe t'i shtoni ato në botë.
- shkelje e atomicitetit funksional.

Le të shohim minusin e fundit në më shumë detaje.

Le të imagjinojmë se kemi një objekt "ObjectA" në të cilin thirret metoda "MethodA". Metoda "MethodA" përbëhet nga tre hapa dhe thërret brenda vetes tre metoda të tjera që kryejnë këto hapa në mënyrë sekuenciale ("MethodA1", "MethodA2" dhe "MethodA3"). Në metodën e dytë “MethodA2”, publikohet një ngjarje. Dhe më pas ndodh sa vijon: të gjithë ata që janë abonuar në këtë ngjarje do të fillojnë ta përpunojnë atë, duke kryer një pjesë të logjikës së tyre. Në këtë logjikë mund të publikohen edhe ngjarje të tjera, përpunimi i të cilave mund të çojë edhe në publikimin e ngjarjeve të reja etj. Pema e publikimeve dhe e reagimeve në disa raste mund të rritet shumë. Zinxhirë të tillë të gjatë janë jashtëzakonisht të vështirë për t'u korrigjuar.
Por problemi më i keq që mund të ndodhë këtu është kur një nga degët e zinxhirit të çon përsëri në "ObjectA" dhe fillon të përpunojë ngjarjen duke thirrur një metodë tjetër "MethodB". Rezulton se metoda jonë "MethodA" nuk i ka përfunduar ende të gjitha hapat, pasi u ndërpre në hapin e dytë dhe tani përmban një gjendje të pavlefshme (në hapat 1 dhe 2 ne ndryshuam gjendjen e objektit, por ndryshimi i fundit nga hapi 3 nuk është bërë ende) dhe në të njëjtën kohë "MethodB" fillon të ekzekutohet në të njëjtin objekt, duke pasur këtë gjendje të pavlefshme. Situata të tilla gjenerojnë gabime, janë shumë të vështira për t'u kapur, çojnë në nevojën për të kontrolluar rendin e metodave të thirrjes dhe publikimit të ngjarjeve kur logjikisht nuk ka nevojë për ta bërë këtë, dhe sjellin një kompleksitet shtesë që dikush do të donte të shmangte.

Zgjidhje

Zgjidhja e problemit të përshkruar nuk është e vështirë, thjesht shtoni funksionalitetin e një përgjigjeje të vonuar në një ngjarje. Si një zbatim i thjeshtë i një funksionaliteti të tillë, ne mund të krijojmë një depo në të cilën do të ruajmë ngjarjet që kanë ndodhur. Kur ndodh një ngjarje, ne nuk e ekzekutojmë atë menjëherë, por thjesht e ruajmë atë diku. Dhe në momentin që vjen radha e ekzekutimit funksional të disa komponentëve në lojë (në metodën Update, për shembull), ne kontrollojmë praninë e ngjarjeve që kanë ndodhur dhe kryejmë përpunim nëse ka ngjarje të tilla.
Kështu, kur ekzekutohet metoda "MethodA", ajo nuk ndërpritet dhe ngjarja e publikuar regjistrohet nga të gjithë të interesuarit në një ruajtje të veçantë. Dhe vetëm pasi abonentët e interesuar të kenë arritur në radhë, ata do ta tërheqin ngjarjen nga ruajtja dhe do ta përpunojnë atë. Në këtë pikë, e gjithë "MethodA" do të përfundojë dhe "ObjectA" do të ketë një gjendje të vlefshme.

konkluzioni

Një lojë kompjuterike është një strukturë komplekse me një numër të madh komponentësh që ndërveprojnë ngushtë me njëri-tjetrin. Ju mund të gjeni shumë mekanizma për organizimin e këtij ndërveprimi, por unë preferoj mekanizmin që përshkrova, bazuar në ngjarje dhe në të cilin arrita përmes rrugës evolucionare të kalimit të të gjitha llojeve të grabitjeve. Shpresoj se dikujt do t'i pëlqejë gjithashtu dhe artikulli im do të sjellë qartësi dhe do të jetë i dobishëm. 2018-06-22T12:41:05+00:00

Problemi i krijimit dhe lëshimit të saktë të objekteve COM në çdo gjuhë të menaxhuar (me një grumbullues mbeturinash) është kompleks dhe i shumëanshëm - kaq shumë është shkruar tashmë për këtë temë dhe megjithatë mosmarrëveshjet dhe keqkuptimet e vazhdueshme lindin ende në forume.

Do të përpiqem të jem i shkurtër dhe konciz, pasi ky artikull është më praktik në natyrë dhe nuk pretendon të jetë e vërteta përfundimtare.

Unë do të përshkruaj vetëm përvojën time në lidhje me përdorimin e OneScript për të komunikuar me bazat e të dhënave 1C nëpërmjet një lidhjeje të jashtme kur niset nga Përditësuesi (megjithëse metoda e nisjes në të vërtetë nuk ka rëndësi).

Në të njëjtën kohë, nuk do të ndalem në vetë konceptin e një objekti COM (në këtë kuptim, i referoj të gjithëve te libri i mrekullueshëm "COM Fundamentals" nga Dale Rogerson).

Gjithashtu, nuk do të ndalem në mënyrën se si objektet COM bashkëjetojnë në gjuhët me menaxhimin automatik të kujtesës, të cilat përfshijnë OneScript.

Ky artikull do të përmbajë vetëm përfundime praktike.

Thelbi i problemit

Por problemi është se gjatë ekzekutimit të kodit përmes një lidhjeje të jashtme me bazën e të dhënave (e cila në vetvete është një objekt COM), krijohen një numër i madh i objekteve COM eksplicite (të cilat ne vetë i deklaruam) dhe implicite.

Dhe nëse këto objekte nuk i shkatërrojmë drejtpërdrejt, atëherë ato shkatërrohen automatikisht sipas renditjes dhe në momentin kur mjedisi i ekzekutimit e sheh të nevojshme ta bëjë këtë.

Në përgjithësi, në një botë ideale kjo nuk duhet të jetë problem dhe bibliotekat COM duhet ta marrin parasysh këtë. Dhe nëse do të ishte gjithmonë kështu, nuk do të më duhej ta shkruaja fare këtë artikull.

Fatkeqësisht, praktika në përgjithësi dhe në lidhje me bibliotekën COM për lidhjen e jashtme me bazat e të dhënave 1C në veçanti tregon se rendi i shkatërrimit të të gjitha objekteve COM duhet të specifikohet në mënyrë eksplicite dhe duhet të jetë e kundërta e renditjes në të cilën janë krijuar.

Dhe nëse nuk e bëni këtë, atëherë skripti ynë do të funksionojë në mënyrë të përsosur në disa kompjuterë (ose me një platformë 1c) dhe në të njëjtën kohë do të dështojë me një gabim në kompjuterë të tjerë (platforma të tjera 1c).

Gabimi do të ndodhë në fund të skriptit kur objektet COM shkatërrohen nga mbledhësi i mbeturinave. Një gabim i tillë do të jetë i paqëndrueshëm dhe, në rastin më të mirë, thjesht do të çojë në mos kompletimin e saktë të lidhjes me bazën e të dhënave. Kjo do të thotë, skripti tashmë do të funksionojë, dhe tastiera e serverit 1c do të tregojë se ka ende një lidhje me bazën e të dhënave.

Në këtë rast, vetë skripti do të funksionojë shkëlqyeshëm dhe do të bëjë gjithçka që duam prej tij, por lidhja me vetë bazën e të dhënave do të përfundojë gabimisht dhe kodi i gabimit nga OneScript më së shpeshti do të jetë -1073741819.

Në të njëjtën kohë, në vetë shembujt e OneScript në Përditësuesin, fillimisht nuk do të bëj një lëshim të qartë të burimeve, në mënyrë që të mos tremb përdoruesit. Në vend të kësaj, unë do të jap një lidhje me këtë artikull me një analizë të shembujve më të thjeshtë.

Shembulli i parë

Le të shohim skriptin më të thjeshtë për shfaqjen e një liste përdoruesish:

Çfarë objektesh COM shohim këtu:

  1. v8- ky objekt është krijuar në mënyrë eksplicite nga përditësuesi dhe është shkatërruar në procedurën AtCompleteWork.
  2. v8.Përdoruesit e Bazës së Informacionit- këtu kemi hyrë në menaxherin e përdoruesit të bazës së informacionit përmes një pike dhe një objekt i ri COM është krijuar në mënyrë implicite nga koha e ekzekutimit të OneScript. Kjo është një situatë e papranueshme për ne, pasi nuk do të mund ta lëshojmë një objekt të tillë në momentin që na nevojitet. Më poshtë do të tregoj se si të shpëtoj nga krijimi i tillë i nënkuptuar i objektit.
  3. Lista e Përdoruesve- ky objekt COM na u kthye me metodën GetUsers.
  4. Përdoruesi- ky objekt COM krijohet në çdo përsëritje të ciklit.

A është kjo e gjitha? Por jo. Këtu ekziston një tjetër objekt COM i krijuar në mënyrë implicite brenda kohës së ekzekutimit. Dhe arsyeja e krijimit të tij është përdorimi i ciklit Për Çdo. Kur përdorni një lak të tillë, krijohet një përsëritës për Listën e Përdoruesve dhe ky përsëritës përmban një objekt të brendshëm COM, të cilin gjithashtu nuk mund ta çlirojmë. Prandaj rregulli i menjëhershëm - duhet të shmangni unazat For Every kur kaloni koleksionet COM.

Ja se si ta rishkruani këtë kod në mënyrë që pas ekzekutimit të tij, të gjitha objektet COM të krijuara në të të lëshohen në mënyrë eksplicite në rendin e kërkuar:

Përdoruesit e bazës së informacionit = të papërcaktuar; Lista e Përdoruesve = E Papërcaktuar ; AttemptInformationBaseUsers = v8. Përdoruesit e bazës së informacionit;<>Lista e përdoruesve = Përdoruesit e bazës së informacionit. GetUsers();<>Raporto(

"Ne shfaqim të gjithë përdoruesit e bazës së të dhënave:"

  1. ) ;
  2. Për Indeksin = 0 Nga Lista e Përdoruesve. Sasia() - 1 Cikli i Përdoruesit = Lista e Përdoruesve. Merr (Indeksi) ;
  3. Raporti (Emri i përdoruesit) ;
  4. ReleaseObject(Përdoruesi);

Shembulli i dytë

Le të supozojmë se ne në mënyrë programatike krijojmë përpunim nga konfigurimi i bazës së të dhënave në mënyrë që të nisim ekzekutimin e tij nga kodi ynë.

Kodi i krijimit të përpunimit do të jetë si ky:

  1. Një objekt COM v8.Processing është krijuar në mënyrë implicite
  2. Objekti COM v8.Processing.ImportCase është krijuar në mënyrë implicite
  3. Një objekt përpunues COM krijohet dhe ruhet në mënyrë eksplicite në variablin LoadModule.

Me këtë kod, ne mund të lëshojmë vetëm në mënyrë eksplicite Modulin e Ngarkesës, por nuk mund të bëjmë asgjë me dy objektet COM të krijuara në mënyrë implicite.

Prandaj, ky kod duhet të rishkruhet si kjo:

Përpunim = E padefinuar ; ImportCase = E papërcaktuar ; LoadModule = I padefinuar ;<>Përpjekje përpunimi = v8. Përpunimi;<>ImportCase = Përpunim. ImportCase;<>LoadModule = ImportCase. Krijo() ;

// pjesa tjetër e kodit...

Përjashtim FundTry;

Nëse ModuleLoad

I padefinuar Pastaj ReleaseObject(LoadModule) ;

FundNëse;

Nëse ImportCase

I padefinuar Pastaj ReleaseObject(ImportCase) ;

FundNëse;

Nëse Trajtimet

I padefinuar Pastaj ReleaseObject(Përpunohet);

FundNëse; Shembulli i tretë) më jepni shembuj të kodit kur nuk keni mundur të arrini lëshimin e saktë të objekteve COM dhe unë do të përpiqem t'ju ndihmoj sa më shumë që të mundem.

Mund të mos shqetësoheni?

Jam dakord që shkrimi i kodit real që liron në mënyrë eksplicite të gjitha objektet COM në rendin e duhur nuk është një detyrë e lehtë, pasi ka shumë mënyra për të "gjuajtur veten në këmbë" në këtë rast.

Ju mund ta shkruani kodin saktësisht sikur të ishte duke u ekzekutuar drejtpërdrejt në bazën e të dhënave dhe të injoroni kodin e gabimit që OneScript ju kthen.

Me këtë strategji, unë rekomandoj rishkrimin e procedurës AtCompleteWork me një pauzë mjaft të gjatë në fund - siç tregon praktika, kjo rrit disi shanset që objektet COM që mbeten në memorie të përfundojnë pa gabime.

Këtu është kodi:

Procedura Në Finish() Nëse v8<>I padefinuar Pastaj Përpjekja për të lëshuarObject(v8) ;<>v8 = E papërcaktuar;<>Përjashtim FundTry; FundNëse; Nëse lidhës I padefinuar Pastaj Përpjekja për të lëshuarObject(lidhësin) ; lidhës = I papërcaktuar; Përjashtim FundTry;

FundNëse;

Nëse përditësuesi



I padefinuar Pastaj Përpjekje për Objekt të Lirë (Përditësues); përditësues = I papërcaktuar;

© 2015 .
FundNëse; | // Prisni në fund të ekzekutimit të programit
| // ndihmon në mënyrë magjike për të shmangur