
Ebben a cikkben megosztom a személyes tapasztalataimat egy kis játék fejlesztéséről Rust nyelven. A működő verzió elkészítése körülbelül 24 órát vett igénybe (főként esténként vagy hétvégén dolgoztam). A játék még messze van a befejezéstől, de úgy gondolom, hogy a tapasztalat hasznos lesz. Elmondom, mit tanultam, és néhány megfigyelést, amit a játék nulláról való felépítése során tettem.
A Skillbox ajánlja: Kétéves gyakorlati tanfolyam .
Emlékeztetjük: minden «Habr» olvasónak 10 000 rubeles kedvezmény jár bármely Skillbox kurzus jelentkezésekor a «Habr» promóciós kód felhasználásával.
Miért Rust?
Ezt a nyelvet választottam, mert sok jót hallottam róla, és látom, hogy egyre népszerűbbé válik a játékfejlesztés területén. A játék írása előtt volt némi tapasztalatom egyszerű alkalmazások fejlesztésével Rust nyelven. Ez pont elegendő volt ahhoz, hogy érezzem a szabadságot a játék írása közben.
Miért játék és mi a játék?
A játékfejlesztés szórakoztató! Szeretném, ha több okom lenne rá, de a „házi” projektekhez olyan témákat választok, amelyek nem túl szorosan kapcsolódnak a szokásos munkámhoz. Milyen játék? Valami tennisz szimulátorfélét szerettem volna csinálni, ahol a Cities Skylines, Zoo Tycoon, Prison Architect és a tennisz kombinálódik. Szóval, egy teniszakadémia játékot hoztam létre, ahova az emberek jönnek játszani.
Technikai előkészületek
Szerettem volna Rust-ot használni, de nem tudtam pontosan, hogy mennyire szükséges a „nulláról” való indulás. Nem akartam pixel shadereket írni és drag-n-drop-ot használni, ezért a legflexibilisebb megoldásokat kerestem.
Hasznos forrásokat találtam, amelyeket megosztok veletek:
- – a játékfejlesztéshez szükséges Rust elemek listája;
Néhány Rust játékmotort tanulmányoztam, végül a Piston és a ggez mellett döntöttem. Ezekkel találkoztam a korábbi projektem során. Végül a ggez tűnt a legmegfelelőbbnek egy kis 2D játék megvalósításához. A Piston moduláris struktúrája túl bonyolult a kezdő fejlesztő számára (vagy annak, aki először dolgozik Rust-tal).
A játék struktúrája
Kicsit időt szántam a projekt architektúrájának átgondolására. Az első lépés, hogy elkészítsük a "földet", az embereket és a teniszpályákat. Az embereknek mozogniuk kell a pályákon, és várakozniuk kell. A játékosoknak olyan készségeik lesznek, amelyek az idő múlásával fejlődnek. Továbbá szükség van egy szerkesztőre, amely lehetővé teszi új emberek és pályák hozzáadását, de ez már nem ingyenes.
Miután mindent átgondoltam, elkezdtem dolgozni.
Játék készítése
Kezdet: körök és absztrakciók
Vettem egy példát a ggez-ből, és egy kört kaptam a képernyőn. Csodálatos! Most egy kis absztrakció. Jó ötletnek tűnt elszakadni a játékelemek ötletétől. Minden elemnek úgy kell megjelenni és frissülni, ahogy itt meg van határozva:
// the game object trait
trait GameObject {
fn update(&mut self, _ctx: &mut Context) -> GameResult<()>;
fn draw(&mut self, ctx: &mut Context) -> GameResult<()>;
}
// a specific game object - Circle
struct Circle {
position: Point2,
}
impl Circle {
fn new(position: Point2) -> Circle {
Circle { position }
}
}
impl GameObject for Circle {
fn update(&mut self, _ctx: &mut Context) -> GameResult<()> {
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> GameResult<()> {
let circle =
graphics::Mesh::new_circle(ctx, graphics::DrawMode::Fill, self.position, 100.0, 2.0)?;
graphics::draw(ctx, &circle, na::Point2::new(0.0, 0.0), 0.0)?;
Ok(())
}
}Ez a kód rész lehetővé tette, hogy kiváló lista objektumokat kapjak, amelyeket frissíthetek és rajzolhatok egy legalább olyan nagyszerű ciklusban.
mpl event::EventHandler for MainState {
fn update(&mut self, context: &mut Context) -> GameResult {
// Frissítse az összes objektumot
for object in self.objects.iter_mut() {
object.update(context)?;
}
Ok(())
}
fn draw(&mut self, context: &mut Context) -> GameResult {
graphics::clear(context);
// Rajzolja az összes objektumot
for object in self.objects.iter_mut() {
object.draw(context)?;
}
graphics::present(context);
Ok(())
}
} A main.rs szükséges, mert itt van minden kódsor. Egy kis időt töltöttem azzal, hogy szétválasszam a fájlokat és optimalizáljam a könyvtárstruktúrát. Így nézett ki minden ezután:
resources -> itt találhatóak az összes erőforrás (képek)
src
— entitások
— game_object.rs
— circle.rs
— main.rs -> fő ciklus
Emberek, pályák és képek
A következő lépés a Person játékelem létrehozása és a képek betöltése. Minden 32*32-es csempék alapján kell épülnie.

Teniszpályák
Miután megnéztem, hogy néznek ki a teniszpályák, úgy döntöttem, hogy 4*2 csempékből készítem őket. Kezdetben készíthettem volna egy olyan méretű képet, vagy összerakhattam volna 8 külön csempét. De később rájöttem, hogy csak két egyedi csempe szükséges, és ennek oka van.
Összesen két ilyen csempénk van: 1 és 2.
A pálya minden szakasza 1-es vagy 2-es csempéből áll. Ezek elhelyezhetők normál módon vagy 180 fokkal elforgatva.

Alapértelmezett építési (összeszerelési) mód
Miután sikerült az arénák, emberek és térképek renderelése, rájöttem, hogy szükség van egy alap összeszerelési módra is. Ezt úgy valósítottam meg, hogy amikor megnyomják a gombot - az elem kijelölődik, a kattintás pedig a megfelelő helyre teszi azt. Így az 1-es gomb lehetővé teszi a pálya kiválasztását, míg a 2-es gomb a játékos kiválasztását.
De emlékeznünk kell arra is, mit jelentenek az 1 és 2, ezért hozzáadtam egy drótvázat, hogy világos legyen, melyik elem van kijelölve. Így néz ki ez.

Kérdések az architektúráról és a refaktorálásról
Most már van néhány játékelem: emberek, pályák és emeletek. De ahhoz, hogy a drótvázak működjenek, minden objektum entitásnak jeleznie kell, hogy az objektumok bemutató módban vannak-e, vagy csak egy keretet rajzoltak. Ez nem túl kényelmes.
Úgy tűnt, hogy újra kell gondolni az architektúrát, hogy néhány korlátozás kiemelkedjen:
- az a tény, hogy van egy entitás, amely megjeleníti és frissíti önmagát, problémát jelent, mivel ez az entitás nem tudja "megtudni", mit kell renderelnie – képet vagy drótvázat;
- hiányzik az eszköz, amely lehetővé teszi a tulajdonságok és viselkedés cseréjét különálló entitások között (példa – is_build_mode tulajdonság vagy a viselkedés rajzolása). Használhatnánk az öröklődést, bár Rustban nincs normális módja ennek megvalósítására. Amire tényleg szükségem volt, az a kompozíció;
- szükséges volt egy eszköz az entitások közötti interakcióhoz, hogy embereket rendelhessek a pályákhoz;
- maga az entitás adatok és logika keveréke volt, ami nagyon gyorsan kicsúszott az irányítás alól.
További kutatást végeztem, és felfedeztem az architektúrát , amelyet általában játékokban használnak. Íme az ECS előnyei:
- az adatok elválasztása a logikától;
- kompozíció az öröklődés helyett;
- adatközpontú architektúra.
Az ECS három alapvető fogalmát jellemzi:
- entitások – az objektum típusa, amelyre az azonosító hivatkozik (ez lehet játékos, labda vagy más);
- összetevők – ezekből állnak az entitások. Példa – renderelő, elhelyezkedés és más összetevők. Ezek adat tárolók;
- rendszerek – ezek használják tanto az objektumokat, mint az összetevőket, plusz tartalmazzák a viselkedést és logikát, amelyek alapjául ezen adatok szolgálnak. Példa – egy renderelő rendszer, amely végigiterálja az összes entitást renderelő összetevőkkel és végzi a rajzolást.
Az áttekintés után világossá vált, hogy az ECS az alábbi problémákat oldja meg:
- a kompozíció alkalmazása az öröklődés helyett az entitások rendszerszervezésében;
- a kód zűrzavarának megszüntetése kezelő rendszerekkel;
- az olyan módszerek használata, mint az is_build_mode, hogy a drótváz logikáját ugyanabban a helyen tároljuk – a renderelő rendszerben.
Íme, mi lett az ECS bevezetése után.
resources -> itt találhatóak az összes erőforrás (képek)
src
— összetevők
— position.rs
— person.rs
— tennis_court.rs
— floor.rs
— wireframe.rs
— mouse_tracked.rs
— források
— mouse.rs
— rendszerek
— rendering.rs
— constants.rs
— utils.rs
— world_factory.rs -> világ gyártó funkciók
— main.rs -> fő ciklus
Emberek kijelölése a pályákra
Az ECS megkönnyítette az életem. Most már volt egy rendszeres módszerem az adatok hozzáadására az entitásokhoz, valamint az ezekre az adatokra alapozott logika hozzáadására. Ez pedig lehetővé tette, hogy megszervezzem az emberek elosztását a pályákra.
Amit tettem:
- adatokat adtam a kijelölt pályákról a Személyhez;
- adatokat adtam a szétosztott emberekről a Teniszpályához;
- hoztam létre egy CourtChoosingSystem-et, amely lehetővé teszi az emberek és pályák elemzését, a szabad pályák felfedezését és a játékosok elosztását rájuk;
- hoztam létre egy PersonMovementSystem-et, amely megkeresi a pályákra kijelölt embereket, és ha ott nincsenek, eljuttatja őket ahová szükséges.

Összegzés
Nagyon élveztem dolgozni ezen az egyszerű játékon. Sőt, elégedett vagyok azzal, hogy Rustot használtam a megírásához, mivel:
- A Rust megadja, amire szükséged van;
- kiváló dokumentációval rendelkezik, a Rust nagyon elegáns;
- a konzisztencia menő;
- nem kell klónoznom, másolnom vagy hasonló műveletekhez folyamodnom, amiket gyakran tettem C++-ban;
- Az Options nagyon kényelmesen használható, és nagyszerűen kezelik a hibákat;
- ha a projekt le tudott fordulni, akkor 99%-ban működik, és éppen úgy, ahogyan kellene. A fordító hibaüzenetei szerintem a legjobbak, amiket valaha láttam.
A játékfejlesztés Ruston most kezdődött el. De már van egy stabil és meglehetősen nagy közösség, amely azon dolgozik, hogy a Rustot mindenki számára elérhetővé tegye. Ezért optimistán tekintek a nyelv jövőjére, izgatottan várom a közös munkánk eredményeit.
A Skillbox ajánlja:
- Online kurzus .
- Gyakorlati kurzus .
- Gyakorlati éves kurzus .
Forrás: habr.com
