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ë?
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
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.
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
();
//Shto një forcë në Trupin e ngurtë.
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.
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ë.
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.
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.
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ërndryshe, mund të krijoni një objekt me këto dy hapa:
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.
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.
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;
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.
Klasa publike UnitDiedEvent ( Lista private vetëm për lexim
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.
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.
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ë.
Le të shohim skriptin më të thjeshtë për shfaqjen e një liste përdoruesish:
Çfarë objektesh COM shohim këtu:
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:"
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:
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
FundNëse;
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.
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; |
Nëse përditësuesi