Ești programator și probabil că ai jocurile tale preferate pe calculator. Perioada de izolare socială pe care o experimentăm poate fi transformată într-un moment propice pentru a -ți găsi un nou hobby. De exemplu, acela de a-ți valorifica potențialul creator și cunoștințele de programare pentru a inventa un joc. Al tău. În articolul de față îți propunem o introducere rapidă în Unity3D prin realizarea unui joc 2D.
Un amănunt important la început de drum. Deși partea tehnică este importantă, ea este doar vârful aisbergului în realizarea un joc de succes. Este important să aveți în vedere grafica, eventual prin colaborarea cu un artist și, de asemenea, sunetele legate de interacțiune și chiar fundalul muzical.
Versiune demo pe testers.ro
Vom acoperi în continuare toate aspectele necesare realizării unui joc 2D.
Primul pas este să instalăm Unity. Vom alege licența Personală care este gratuită. Cu toate că există un editor încorporat, MonoDevelop, vă sugerez să instalați și Visual Studio pentru a ne bucura de code completion și alte avantaje oferite de un IDE modern.
Vom avea nevoie de un caracter și diferite imagini pentru background, obiecte din joc. Pentru aceasta vă sugerez să le descărcați de pe AppStore-ul Unity. Sunt disponibile mai multe opțiuni gratuite. Bineînțeles, dacă sunteți sau aveți un prieten artist, puteți să aveți un avantaj încă de la început.
Este un aspect important pentru a crea atmosfera dorită. S-ar putea ca detectarea celui mai potrivit să vă consume mai mult timp decât v-ați așteptat inițial. Pentru început, vă sugerez să vă uitați la sunetele gratuite puse la dispoziție de YouTube.
Înainte de a trece la realizarea de jocuri vom parcurge partea de teorie. Vor fi prezentate în continuare conceptele principale pentru realizarea unui joc în Unity cu accent în partea de 2D.
Reprezintă clasa de bază a tuturor obiectelor ce apar într-o scenă. O caracteristică importantă este că pot să îi fie adăugate componente care vor defini comportamentul acestuia. Totodată, componenta Transform se regăsește în toate obiectele.
Definește caracteristicele de poziție, rotație și scalare a obiectului din scenă. Poziția inițială (0,0) se găsește în centrul scenei. Un amănunt semnificativ este cel legat de obiectele copil (child), poziția acestora este în relație cu obiectul părinte (parent) și nu la scenă.
Așa cum vom vedea mai târziu, controlul componentei Transform din codul sursă C# ne va permite modificarea poziției caracterului principal.
Prefabs este un concept cheie în jocurile Unity din perspectiva reutilizării obiectelor. Reprezintă modul în care putem programatic să creăm clone ale oricărui GameObject. Concret, printr-o simplă operație de mutare a unui obiect din scena Scena noastră în tabul de proiect se realizează un prefab. Folosind această modalitate, putem să creăm rapid o multitudine de inamici sau proiectile.
Reprezintă modalități de a grupa obiectele pentru a putea fi găsite: taguri sau pentru a defini interacțiunea între acestea: layere.
Atașarea componentei Rigidbody unui obiect din scena Unity determină controlul mișcării acestuia bazate pe simularea legilor fizicii. Se pot defini diferiți parametri, inclusiv valoarea accelerației gravitaționale. Detectarea interacțiunii dintre diferite obiecte se realizează prin intermediul unui colider. Acesta reprezintă un model spațial atașat obiectului nostru, astfel încât să îi simuleze forma. În funcție de complexitatea dorită, putem să folosim unul primitiv de forma unui dreptunghi sau circ, unul compus, mesh colider, colider static .
În momentul unui impact se vor apela metodele: OnCollisionEnter2D()
, OnCollisionStay2D()
și OnCollisionExit2D()
pentru fiecare obiect implicat.
Deși prin interfața grafică a IDE-ului Unity putem realiza multe prin adăugarea componentelor și definirea parametrilor, scripturile sunt cele care ne dau libertate în realizarea jocurilor. Acestea sunt scrise în C#, iar unui obiect putem să îi asociem unul sau mai multe scripturi.
Accesul la componentele obiectului căruia îi este atașat scriptul se realizează prin GetComponent<>()
:
Rigidbody2D rigidbody2DComp =
GetComponent();
rigidbody2DComp.AddForce(Vector2.up * 60,
ForceMode2D.Force);
În cazul în care avem atașate obiecte copil (child), putem să le accesăm prin Find():
Transform leftCheck = transform.Find("LeftCheck");
Fiecare script are următoarele metode legate de lifecycle-ul instanțelor:
Start()
- este apelată o singură dată la inițializareUpdate()
- este apelată o dată pentru fiecare frame , fiind locul unde se realizează operațiile legate de afișarea elementelor grafice.FixedUpdate()
- este apelată pentru realizarea operațiilor legate de sistemul fizic de interacțiune a obiectelor. Avem garanția că va fi apelată în fiecare frame indiferent de puterea de procesare.Un amănunt important, fiecare variabilă membră a clasei care este declarată publică va apărea ca o proprietate în controllerul scriptului. Putem astfel să modificăm, să asignăm valori, să modificăm comportamentul unei instanțe fără a modifica codul sursă.
Reprezintă o modalitate de a realiza operațiuni repetitive fără a bloca fluxul de execuție. Practic se vor realiza câteva operații în fiecare frame, după care se va continua în următorul.
public IEnumerator ShowBlinkingMessage()
{
while (true)
{
gameMsg.enabled = !gameMsg.enabled;
yield return new WaitForSeconds(0.1f);
}
}
În cazul de mai sus realizăm o clipire a unui mesaj prin alternarea vizibilității sale o dată la 10 milisecunde. Corutinele trebuie să returneze un IEnumerator și vor fi apelate astfel:
StartCoroutine(ShowBlinkingMessage());
Condițiile de exit pot să fie mai complexe precum cea de mai jos, care se va aștepta atât timp cât clipul audio rulează.
yield return new WaitWhile(() => sound.isPlaying);
Creăm un nou proiect Unity și alegem opțiunea 2D.
Adăugăm în scenă un nou GameObject: Right click -> Create Empty. Îl redenumim Player.
Deschidem AssetStore-ul și căutăm un kit de asseturi 2D: alegem Sunny Land.
Odată descărcat selectăm Import. Asset->Sunnyland va apărea în panelul proiectului .
Mergem în folderul AssetsSunnylandartworkSpritesplayeridle și selectăm toate cele patru resurse: player-idle-1 ... 4. Realizăm un drag & drop al acestora peste obiectul Player.
Personajul nostru ar trebui deja să se vadă în scenă. Pentru o vizibilitate mai bună, îl vom mări. În componenta Transform vom modifica valorile Scale X și Y în 5.
Suntem gata pentru prima rulare. Selectați butonul de play și veți vedea personajul nostru animat.
În continuare, vom adăuga un teren peste care se va mișca caracterul. Primul pas este să creăm un Tilemap:
Click de dreapta în panelul Hierarchy și selectăm 2D Object -> Tilemap. Va fi create un obiect Grid cu un copil Tilemap.
Selectăm din meniul Window opțiunea 2D->Tile Palette, iar fereastra afișată o vom muta în panelul din dreapta alături de taburile Inspector și Console.
Alegem Create New Palette și creăm un tilemap numit Ground.
Din Tile Palette selectăm opțiunea de Paint (B) selectăm un tile pentru pământ și desenăm o bază. Vom observa că elementele sunt prea mici desenate în scenă. Pentru aceasta va trebui să redimensionăm celulele.
Selectăm elementul Grid, iar în componenta cu același nume vom modifica Cell Size din 1 în 0.16.
Acum putem re-desena pământul. În cazul în care trebuie să ștergeți elementele deja adăugate țineți apăsată tasta Shift și dați click pe element.
Adăugăm o componentă Box Collider 2D obiectului Grid->Tilemap
Am terminat de definit terenul. Revenim la personajul principal, căruia îi vom adăuga un RigidBody pentru interacțiunile fizice și un colider pentru detectarea acestuia.
Selectăm obiectul Player și îi adăugăm o componentă Rigidbody 2D.
Modificăm poziția playerului astfel încât să fie deasupra solului. Rulăm aplicația și vom observa cum caracterul nostru coboară datorită forței gravitaționale, trece prin pământ și dispare. Pentru a fixa această problemă va trebui să îi adăugăm un collider astfel încât interacțiunea sa să fie detectată
În continuare vom controla mișcarea acestuia. Primul pas este creăm un folder nou Scripts în folder-ul Assets și să creăm în acesta un script C# numit PlayerScript
public class PlayerScript : MonoBehaviour
{
public int speed = 50;
public int jumpParam = 40;
private Rigidbody2D rigidBody2DComponent;
private Vector2 movement;
bool isJumping = false;
// Start is called before the first frame update
void Start()
{
rigidBody2DComponent = GetComponent();
movement = new Vector2();
}
public void OnCollisionEnter2D(Collision2D collision)
{
isJumping = false;
}
public void OnCollisionExit2D(Collision2D collision)
{
isJumping = true;
}
private void FixedUpdate()
{
movement.x = Input.GetAxis("Horizontal");
movement.y = Input.GetAxis("Vertical");
movement.Normalize();
rigidBody2DComponent.velocity = new Vector2(movement.x * Time.deltaTime * speed, rigidBody2DComponent.velocity.y);
if (movement.y != 0 && !isJumping)
{
rigidBody2DComponent.AddForce(Vector2.up*jumpParam);
}
}
}
Acesta este scriptul prin care deplasăm caracterul nostru. Metoda principală este FixedUpdate() unde citim și normalizăm apăsarea tastelor cursor. Deplasarea pe axa X este dată de viteza imprimată componentei RigidBody. Se remarcă Time.deltaTime și care reprezintă timpul cât a durat execuția de la ultimul frame. În felul acesta, viteza de mișcare a caracterului nostru va fi aceeași indiferent de numărul de frame-uri și GPU-ul calculatorului.
Pentru acțiunea de a sări, adăugăm o forță verticală componentei Rigidbody pe care o multiplicăm cu un anumit factor, jumpParam. Deoarece metoda FixedUpdate() se va executa de mai multe ori pe secundă, aplicarea unei forțe de fiecare dată când ținem apăsată o tastă va rezulta în dispariția rapidă pe verticală a caracterului din ecran în cazul unui salt. Soluția este să detectăm coliziunile cu pământul, iar aceasta se realizează prin folosirea unei variabile isJumping. Aceasta are valoarea true de fiecare dată când nu este în contact cu un collider prin metoda OnCollisionExit2D().
Asociem scriptul cu obiectul Player și rulăm aplicația. Caracterul nostru se va mișca și sări. Putem să adăugăm câteva trepte pe care acesta să poată urca. Vom observa în acest caz rotirea acestuia - ceea ce nu este de dorit. Soluția la această problemă este să anulăm mișcarea pe axa Z astfel: în Player->Rigidbody->Constrains selectăm opțiunea Freeze Rotation Z.
Sperăm că v-a plăcut această primă introducere în Unity. Vom continua în numărul următor al revistei cu un nou articol în care veți descoperi cum să interacționăm cu alte obiecte, să culegem monede/puncte, să adăugăm scorul și muzică jocului.
de Andra Chicoș
de Iulia Creta , Bogdan Barza