Այս հոդվածում ես կխոսեմ Rust-ում փոքրիկ խաղ մշակելու իմ անձնական փորձի մասին: Աշխատանքային տարբերակ ստեղծելու համար պահանջվեց մոտ 24 ժամ (հիմնականում աշխատում էի երեկոյան կամ հանգստյան օրերին): Խաղը շատ հեռու է ավարտից, բայց կարծում եմ, որ փորձը հատուցող կլինի: Ես կկիսվեմ իմ սովորածով և որոշ դիտարկումներով, որոնք արել եմ խաղը զրոյից կառուցելիս:
Հիշեցում.«Habr»-ի բոլոր ընթերցողների համար՝ 10 ռուբլի զեղչ «Habr» գովազդային կոդով Skillbox-ի ցանկացած դասընթացին գրանցվելիս:
Ինչու՞ Ռաստ:
Ես ընտրեցի այս լեզուն, քանի որ դրա մասին շատ լավ բաներ եմ լսել և տեսնում եմ, որ այն գնալով ավելի տարածված է դառնում խաղերի մշակման մեջ: Նախքան խաղը գրելը Rust-ում պարզ հավելվածներ մշակելու քիչ փորձ ունեի: Սա բավական էր ինձ ազատության զգացում տալու համար խաղը գրելիս:
Ինչու՞ խաղ և ինչպիսի՞ խաղ:
Խաղեր պատրաստելը զվարճալի է: Կցանկանայի, որ ավելի շատ պատճառներ լինեին, բայց «տնային» նախագծերի համար ես ընտրում եմ թեմաներ, որոնք այնքան էլ սերտորեն կապված չեն իմ սովորական աշխատանքի հետ: Սա ի՞նչ խաղ է։ Ես ուզում էի թենիսի սիմուլյատորի նման մի բան պատրաստել, որը միավորում է Cities Skylines-ը, Zoo Tycoon-ը, Prison Architect-ը և հենց թենիսը: Ընդհանրապես, դա թենիսի ակադեմիայի մասին խաղ էր, որտեղ մարդիկ գալիս են խաղալու։
Տեխնիկական ուսուցում
Ես ուզում էի օգտագործել Rust-ը, բայց հստակ չգիտեի, թե ինչքան հիմք կպահանջվի սկսելու համար: Ես չէի ուզում գրել պիքսելային շեյդերներ և օգտագործել drag-n-drop, ուստի փնտրում էի ամենաճկուն լուծումները:
Ես գտա օգտակար ռեսուրսներ, որոնք ես կիսում եմ ձեզ հետ.
Ես ուսումնասիրեցի Rust խաղի մի քանի շարժիչներ՝ ի վերջո ընտրելով Piston-ը և ggez-ը: Ես նրանց հանդիպեցի նախորդ նախագծի վրա աշխատելիս: Ի վերջո, ես ընտրեցի ggez-ը, քանի որ այն ավելի հարմար էր թվում փոքր 2D խաղ իրականացնելու համար։ Պիստոնի մոդուլային կառուցվածքը չափազանց բարդ է սկսնակ մշակողի համար (կամ մեկի համար, ով առաջին անգամ է աշխատում Rust-ի հետ):
Խաղի կառուցվածքը
Ես որոշ ժամանակ անցկացրի՝ մտածելով նախագծի ճարտարապետության մասին: Առաջին քայլը «հող», մարդիկ ու թենիսի կորտեր պատրաստելն է։ Մարդիկ պետք է շրջվեն դատարաններով և սպասեն։ Խաղացողները պետք է ունենան հմտություններ, որոնք ժամանակի ընթացքում բարելավվում են: Բացի այդ, պետք է լինի խմբագիր, որը թույլ կտա ավելացնել նոր մարդկանց և դատարաններ, բայց դա այլևս անվճար չէ:
Ամեն ինչ մտածելով, ես գործի անցա։
Խաղի ստեղծում
Սկիզբ՝ շրջանակներ և աբստրակցիաներ
Ես օրինակ վերցրեցի ggez-ից և էկրանին շրջան ստացա։ Հրաշալի՜ Հիմա մի քանի աբստրակցիա. Ես մտածեցի, որ լավ կլինի վերացական լինել խաղային օբյեկտի գաղափարից: Յուրաքանչյուր օբյեկտ պետք է ներկայացվի և թարմացվի, ինչպես նշված է այստեղ.
// 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(())
}
}
Կոդի այս կտորն ինձ տվեց օբյեկտների գեղեցիկ ցանկ, որոնք ես կարող էի թարմացնել և ներկայացնել նույնքան գեղեցիկ օղակով:
mpl event::EventHandler for MainState {
fn update(&mut self, context: &mut Context) -> GameResult<()> {
// Update all objects
for object in self.objects.iter_mut() {
object.update(context)?;
}
Ok(())
}
fn draw(&mut self, context: &mut Context) -> GameResult<()> {
graphics::clear(context);
// Draw all objects
for object in self.objects.iter_mut() {
object.draw(context)?;
}
graphics::present(context);
Ok(())
}
}
main.rs-ն անհրաժեշտ է, քանի որ այն պարունակում է կոդերի բոլոր տողերը: Ես մի փոքր ժամանակ ծախսեցի ֆայլերը առանձնացնելու և գրացուցակի կառուցվածքի օպտիմալացման վրա: Ահա թե ինչ տեսք ուներ դրանից հետո. ռեսուրսներ -> այստեղ են բոլոր ակտիվները (պատկերներ)
src
- սուբյեկտներ
— game_object.rs
- շրջան.rs
— main.rs -> հիմնական հանգույց
Մարդիկ, հատակներ և պատկերներ
Հաջորդ քայլը Անձի խաղի օբյեկտ ստեղծելն է և պատկերների բեռնումը: Ամեն ինչ պետք է կառուցվի 32*32 սալիկների հիման վրա։
Թենիսի կորտեր
Ուսումնասիրելով թենիսի կորտերն ինչպիսի տեսք ունեն՝ որոշեցի դրանք պատրաստել 4*2 սալիկներից։ Սկզբում հնարավոր էր այս չափի պատկեր պատրաստել, կամ 8 առանձին սալիկ հավաքել։ Բայց հետո հասկացա, որ ընդամենը երկու եզակի սալիկ է անհրաժեշտ, և ահա թե ինչու.
Ընդհանուր առմամբ մենք ունենք երկու այդպիսի սալիկներ՝ 1 և 2։
Կորտի յուրաքանչյուր հատված բաղկացած է սալիկից 1 կամ 2:
Հիմնական շինարարական (հավաքման) ռեժիմ
Այն բանից հետո, երբ ինձ հաջողվեց հասնել կայքերի, մարդկանց և քարտեզների մատուցման, ես հասկացա, որ անհրաժեշտ է նաև հավաքման հիմնական ռեժիմ: Ես դա իրականացրել եմ այսպես՝ երբ կոճակը սեղմվում է, օբյեկտն ընտրվում է, իսկ սեղմումը տեղադրում է այն ցանկալի տեղում։ Այսպիսով, կոճակ 1-ը թույլ է տալիս ընտրել կորտ, իսկ կոճակը 2-ը թույլ է տալիս ընտրել խաղացող:
Բայց մենք դեռ պետք է հիշենք, թե ինչ են նշանակում 1-ը և 2-ը, այնպես որ ես ավելացրեցի լարային շրջանակ, որպեսզի պարզ լինի, թե որ օբյեկտն է ընտրվել: Ահա թե ինչ տեսք ունի.
Ճարտարապետության և վերամշակման հարցեր
Այժմ ես ունեմ մի քանի խաղային առարկաներ՝ մարդիկ, կորտեր և հարկեր։ Բայց որպեսզի լարային շրջանակները աշխատեն, յուրաքանչյուր օբյեկտի էություն պետք է ասվի՝ արդյոք օբյեկտներն իրենք ցուցադրական ռեժիմում են, թե՞ շրջանակը պարզապես գծված է: Սա այնքան էլ հարմար չէ։
Ինձ թվում էր, որ ճարտարապետությունը պետք է վերաիմաստավորվի այնպես, որ բացահայտի որոշ սահմանափակումներ.
Ինքնարտադրող և թարմացնող կազմակերպություն ունենալը խնդիր է, քանի որ այդ կազմակերպությունը չի կարողանա «իմանալ» այն, ինչ պետք է արտաբերի՝ պատկեր և լարային շրջանակ.
Առանձին սուբյեկտների միջև հատկությունների և վարքագծի փոխանակման գործիքի բացակայություն (օրինակ՝ is_build_mode հատկությունը կամ վարքագծի ձևավորումը): Հնարավոր կլիներ օգտագործել ժառանգությունը, թեև Rust-ում դրա իրականացման պատշաճ ձև չկա: Ինձ իսկապես անհրաժեշտ էր դասավորությունը.
անհրաժեշտ էր սուբյեկտների միջև փոխգործակցության գործիք՝ մարդկանց դատարաններ նշանակելու համար.
սուբյեկտներն իրենք տվյալների և տրամաբանության խառնուրդ էին, որոնք արագորեն դուրս եկան վերահսկողությունից:
Ես ևս մի քանի հետազոտություն կատարեցի և բացահայտեցի ճարտարապետությունը ECS - Entity Component System, որը սովորաբար օգտագործվում է խաղերում։ Ահա ECS-ի առավելությունները.
տվյալները տարանջատված են տրամաբանությունից.
կազմը ժառանգության փոխարեն;
տվյալների կենտրոնացված ճարտարապետություն:
ECS-ը բնութագրվում է երեք հիմնական հասկացություններով.
սուբյեկտներ - օբյեկտի տեսակը, որին վերաբերում է նույնացուցիչը (դա կարող է լինել խաղացող, գնդակ կամ այլ բան);
բաղադրիչներ - սուբյեկտները կազմված են դրանցից: Օրինակ՝ կոմպոնենտի, տեղանքների և այլոց մատուցում: Սրանք տվյալների պահեստներ են.
համակարգեր - դրանք օգտագործում են և՛ առարկաներ, և՛ բաղադրիչներ, գումարած պարունակում են վարքագիծ և տրամաբանություն, որոնք հիմնված են այս տվյալների վրա: Օրինակ՝ արտապատկերման համակարգ, որը կրկնում է բոլոր սուբյեկտները, որոնք ունեն ռենդերային բաղադրիչներ և կատարում է արտապատկերումը:
Այն ուսումնասիրելուց հետո պարզ դարձավ, որ ECS-ը լուծում է հետևյալ խնդիրները.
սուբյեկտները համակարգված կազմակերպելու համար ժառանգության փոխարեն դասավորության օգտագործումը.
կոդերի խառնաշփոթից ազատվել կառավարման համակարգերի միջոցով.
օգտագործելով մեթոդներ, ինչպիսին է is_build_mode-ը, որպեսզի պահի wireframe տրամաբանությունը նույն տեղում՝ մատուցման համակարգում:
Ահա թե ինչ եղավ ECS-ի ներդրումից հետո։
ռեսուրսներ -> այստեղ են բոլոր ակտիվները (պատկերներ)
src
- բաղադրիչներ
— դիրքորոշում.rs
— մարդ.rs
— tennis_court.rs
— հատակ.rs
- wireframe.rs
— mouse_tracked.rs
- ռեսուրսներ
— mouse.rs
- համակարգեր
— rendering.rs
— հաստատուններ.rs
— utils.rs
— world_factory.rs -> համաշխարհային գործարանի գործառույթները
— main.rs -> հիմնական հանգույց
Մենք մարդկանց նշանակում ենք դատարաններ
ECS-ն հեշտացրել է կյանքը: Հիմա ես ունեի սուբյեկտներին տվյալներ ավելացնելու և այդ տվյալների հիման վրա տրամաբանություն ավելացնելու համակարգված միջոց: Իսկ դա իր հերթին հնարավորություն տվեց կազմակերպել մարդկանց բաշխումը դատարանների միջև։
Ինչ եմ արել.
Անձին ավելացրել է հանձնարարված դատարանների վերաբերյալ տվյալներ.
ավելացրել է տվյալներ բաշխված մարդկանց մասին TennisCourt-ին;
ավելացրել է CourtChoosingSystem-ը, որը թույլ է տալիս վերլուծել մարդկանց և դատարանները, հայտնաբերել հասանելի կորտերը և խաղացողներին բաժանել նրանց;
ավելացրել է PersonMovementSystem-ը, որը փնտրում է դատարաններում նշանակված մարդկանց, և եթե նրանք այնտեղ չեն, ապա ուղարկում է մարդկանց այնտեղ, որտեղ նրանք պետք է լինեն:
Ամփոփելով
Ինձ շատ դուր եկավ այս պարզ խաղի վրա աշխատելը: Ավելին, ես ուրախ եմ, որ այն գրելու համար օգտագործել եմ Rust-ը, քանի որ.
Ժանգը տալիս է այն, ինչ ձեզ հարկավոր է.
այն ունի գերազանց փաստաթղթեր, Rust-ը բավականին էլեգանտ է.
հետեւողականությունը թույն է;
դուք պետք չէ դիմել կլոնավորման, պատճենահանման կամ այլ նմանատիպ գործողությունների, որոնք ես հաճախ եմ արել C++-ում;
Ընտրանքները շատ հեշտ են օգտագործել և շատ լավ են վերաբերվում սխալներին.
եթե նախագիծը կարողացավ կազմվել, ապա 99% դեպքերում այն աշխատում է, և ճիշտ այնպես, ինչպես պետք է: Կարծում եմ, որ կոմպիլյատորի սխալի հաղորդագրությունները լավագույնն են, ինչ ես տեսել եմ:
Rust-ում խաղի մշակումը նոր է սկսվում: Բայց արդեն կա կայուն և բավականին մեծ համայնք, որն աշխատում է Rust-ը բացել բոլորի համար: Ուստի լավատեսորեն եմ նայում լեզվի ապագային՝ անհամբեր սպասելով մեր ընդհանուր աշխատանքի արդյունքներին։