Ovo je poglavlje 1 moje Rust-Bevy tutorial serije "The Impatient Programmer's Guide to Bevy and Rust: Build a Video Game from Scratch". Pridružite se našoj zajednici da biste bili ažurirani o novim izdanjima u ovoj seriji. izvorni kod za ovo poglavlje je dostupan ovdje. Pridruži se našoj zajednici Dostupno ovde At the end of this chapter, you’ll be able to achieve the results in the GIF below: Upute postavljanje postavljanje Ako niste postavili U svakom slučaju, molimo pratite Da ga postavim. odmor Službeni vodič Create a fresh project: cargo new bevy_game cd bevy_game Open and add Bevy: Cargo.toml [dependencies] bevy = "0.16.1" Pretpostavljam da imate iskustva u programiranju na bilo kojem jeziku kao što su javascript ili python. Razmišljanje u sistemima Šta nam je potrebno da izgradimo jednostavnu igru koja omogućuje igračima da se kreću od ulaza na tipkovnici? World System: Stvorite igru u kojoj igrač može da se kreće. Ulazni sistem: Pratite ulaz tipkovnice i pretvorite ga u pokret igrača. Vidimo gore navedeni uzorak vrlo uobičajen u izgradnji igara, uzorak stvaranja / podešavanja i uzorak praćenja promjena stanja igre i ažuriranja entiteta u igri. Setup: Spawn neprijatelja sa različitim ponašanjima, učitajte neprijateljske sprites i animacije, konfigurišite zdravlje neprijatelja i štete vrednosti. Update: Premjestite neprijatelje prema igraču, proverite da li su neprijatelji pogođeni metkama, uklonite mrtve neprijatelje i rađajte nove neprijatelje u intervalima. U srži Bevy-a imamo ovaj jednostavan Ovaj osnovni uzorak je ono što čini Bevy i moćan i jednostavan za razumevanje - kada jednom shvatite ovaj koncept, izgradnja s bevy postaje lakša. setup and update system Zamislite funkciju nazvanu podešavanje, koja vam daje „ukaze“ kao argument, a to vam daje sposobnost da stvorite ili rađate sve što želite. // Pseudo code, doesn't compile fn setup(mut commands: Commands) { // Create anything you want commands.spawn() } What’s mut? U Rust-u moramo izričito spomenuti da planiramo da promijenimo ovu vrednost. Podrazumevano, Rust tretira deklarirane vrednosti kao samo čitave. kaže Rust planiramo da promijenimo vrednost. Rust koristi ovo znanje kako bi sprečio čitavu klasu bugova. mut mut Korišćenje ? And what’s this mut commands: Commands To je iz bevy biblioteke, koja vam omogućuje da dodate stvari u vaš svijet igre. Rust voli eksplicitne tipove, pa omogućuje kompilatoru da zna ovaj parametar je Bevy's komandni interfejs. Preskočite nagovještaj i Rust ne može biti siguran što se pojavljuje, pa blokira izgradnju. :Commands What’s a type? Tip govori Rust koju vrstu vrednosti se bave – brojevi, tekst, časova, Bevy komande, i tako dalje. Isn’t this too much work? To sprečava greške i pomaže vam sa performansi, npr: Ako pokušate da dodate niz na broj, kompilator vas zaustavlja prije nego što igra radi. A je samo dva broja, tako da Rust skladišti tačno dva broja, bez iznenađenja dodatni prostor, koji održava stvari brzo kada imate hiljade entiteta igre. Vec2 Kada kreirate Vec2 objekat u JavaScript ili Python, ne samo da čuvate dva broja. Runtime dodaje metapodatke tipa, informacije o svojstvima i reference prototipa - pretvarajući vašu jednostavnu 8-bitnu strukturu podataka u ~48 bajta memorije. Kada deklarirate Vec2 strukturu sa dva f32 polja, to je upravo ono što se pohranjuje - samo 8 bajta, nema dodatnih metapodataka. Uz 1000 entiteta igre, ta razlika postaje dramatična: Language Memory Usage Overhead Rust 8KB None Dynamic language ~48KB+ 6x overhead odmor 8 kB Niko Dinamički jezik • 48 kB 6x iznad glave Ovo nije samo o upotrebi memorije - to je o performansi.Manje, predvidive rasporede memorije znače bolju upotrebu CPU-a, što se direktno prevodi u brže stope kadrova u igrama u kojima obrađujete hiljade entiteta svakog kadra. kompilator hvata greške tipkanja prije nego što se igra pokrene, a tesno pakiranje memorije održava ga brzo. Postavljanje kamere What should we setup first? Potreban nam je fotoaparat jer ništa ne pokazuje na ekranu bez njega.Svijet može postojati u podacima, ali kamera odlučuje što se zapravo crta. //Pseudo code, don't use this yet fn setup(mut commands: Commands) { commands.spawn(Camera2d) } What’s Camera2d? je Bevy je ugrađen 2D kamera paket. Spawn to i dobićete spremni za odlazak pogled na vaš 2D scenu. Camera2d What’s a bundle? Pakovanje je samo skupina komponenti koje često spojevate zajedno. Na primer, Bevy brodovi koji pakira poziciju, teksturu, boju i vidljivost; uzgoj tog paketa daje entitet sprite u jednom pozivu. SpriteBundle Registracija naše funkcije postavljanja Moramo registrirati našu funkciju podešavanja sa bevy da bi se pokrenula na Startupu. ažuriranje vašeg uz sledeći kod. src/main.rs // Replace your main.rs with the following code. use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .run(); } fn setup(mut commands: Commands) { commands.spawn(Camera2d); } What’s bevy::prelude? je kao starter kit, stvari koje stalno posegnete za kada gradite igru - U pitanju je , komponente, matematički pomoćnici itd. bevy::prelude App Commands What’s App::new()…? kreira aplikaciju za igru, reprodukcija opterećenja, ulaz, audio i drugih sistema, registruje našu funkciju pokretanja i rukama kontrolu do glavnog kruga Bevy. App::new() add_plugins(DefaultPlugins) add_systems(Startup, setup) run() Why register a startup function? What does it do? Mi ih koristimo za podešavanje kamera, igrača i drugih stvari koje bi trebale postojati čim se prozor otvori. What’s bevy main loop? Nakon pokretanja, Bevy ulazi u svoj glavni krug: anketira unos, pokreće vaše sustave, ažurira svijet i prikazuje okvir. Hajde da trčimo cargo run Da, mi smo samo postavili kameru, sad da dodamo naš igrač. Postavljanje igrača Sada kreirajmo našeg igrača! Zapamtite naš i Od ranije? pa, u Bevy, sve je sa Takvi sustavi mogu raditi. Setup System Update System Entity Components Razmislite o entitetu kao jedinstvenom ID-u (kao što je broj socijalne sigurnosti) i komponentima kao podacima koji su povezani s njim. Za našeg igrača, potrebne su nam komponente kao što su brzina kretanja, zdravlje ili posebne sposobnosti. . struct je jedan od ključnih građevinskih blokova rude. Skuplja slične podatke zajedno. Ovde objavljujemo prazno struktur tako da tip sam djeluje kao oznaka možemo pričvrstiti na entitet igrača. Kasnije možemo dodati stvari kao što su zdravlje igrača. struct Player // Place this before main function in main.rs #[derive(Component)] struct Player; Why tag? Tag označava entitet za kasnije pretraživanje. jer ako je vezan samo za našeg junaka, sistemi mogu pitati Bevy, "daj mi entitet sa tagom igrača" i raditi s upravo onim. Player What’s this #[derive(component)]? kaže Rust da priloži makro kôd komponente ovom strukturu. Makro je Rustov način generiranja unaprijed definisanog koda predloška za vas. automatski ubrizgava boilerplate potrebe Bevy, tako da može pohraniti i pronaći entiteta, spašavajući nas od kopiranja istog ljepljivog koda posvuda. Razmotrićemo makroe bliže kasnije u seriji. derive #[derive(Component)] Player What’s a component, and why should the player be a component? Komponenta je komad podataka povezan s entitetom. Položaj, brzina, zdravlje, pa čak i ideja "ovo je igrač" svi žive u komponentama. komponentu koju možemo upitati za taj entitet kasnije, dodati više komponenti (kao što su zdravlje ili zaliha) i pustiti Bevy sisteme da izaberu tačno entitete koje trebaju ažurirati. Player Za sada ćemo predstavljati naš lik na zaslonu pomoću simbola "@". // Replace existing setup function in main.rs with the following code fn setup(mut commands: Commands) { commands.spawn(Camera2d); // Code Update Alert // Append the following lines to your setup function. commands.spawn(( Text2d::new("@"), TextFont { font_size: 12.0, font: default(), ..default() }, TextColor(Color::WHITE), Transform::from_translation(Vec3::ZERO), Player, )); } uzima tu tuplu i tretira je kao paket komponenti. Ovaj jedinstveni poziv dodaje tekst koji želimo prikazati, njegove postavke fontova, boju, poziciju i Tag koji identificira entitet. commands.spawn(( ... )) Player What’s a tuple? Tuple je poredna lista vrednosti napisanih u parantezama. Rust prati svaku poziciju, tako da drži dvije vrednosti pored sebe bez potrebe za stvaranjem strukture. (Text2d::new("@"), TextColor(Color::WHITE)) Whats an entity? Entitet je jedinstveni ID koji Bevy koristi za vezanje komponenti zajedno. Sam po sebi ne sadrži podatke, ali kada dodate komponente, taj ID predstavlja nešto u vašem igralištu. Svaki paket proizvodi novu entitetu s jedinstvenim ID-jem i navedenim komponentama, koje možete prikazati ovako: Entity Components it carries #42 Camera2d #43 , , , , Text2d("@") TextFont TextColor Transform Player # 42 Camera2d # 43 U pitanju je U pitanju je U pitanju je U pitanju je Text2d("@") TextFont TextColor Transform Player Jednom kada se čeka, te entitete žive u svijetu, spremne za sustave da ih otkriju oznakama (komponentima) koje nose. Uvođenje pokreta igrača Hajde da stvorimo naše za pokret igrača! Razmislite o tome što nam je potrebno da pomaknemo igrača: Update System Tipkovnica ulaz - da znaju koje ključeve su pritisnuti Vreme - da bi kretanje bilo glatko bez obzira na brzinu okvira Položaj igrača - da stvarno pomakne igrača U drugim motorima igara, provodili biste vrijeme ručno povezujući ove sustave zajedno.Ali evo čarolije Bevy-a - samo tražite ono što vam je potrebno u vašim parametrima funkcija, a Bevy ga automatski pruža! Hajde da napišemo našu funkciju pokretnog igrača. // Append this code to main.rs fn move_player( // "Bevy, give me keyboard input" input: Res<ButtonInput<KeyCode>>, // "Bevy, give me the game timer" time: Res<Time>, // "Bevy, give me the player's position" mut player_transform: Single<&mut Transform, With<Player>>, ) { let mut direction = Vec2::ZERO; if input.pressed(KeyCode::ArrowLeft) { direction.x -= 1.0; } if input.pressed(KeyCode::ArrowRight) { direction.x += 1.0; } if input.pressed(KeyCode::ArrowUp) { direction.y += 1.0; } if input.pressed(KeyCode::ArrowDown) { direction.y -= 1.0; } if direction != Vec2::ZERO { let speed = 300.0; // pixels per second let delta = direction.normalize() * speed * time.delta_secs(); player_transform.translation.x += delta.x; player_transform.translation.y += delta.y; } } What’s Res? Res ili resursi su komadići informacija širom igre koji nisu vezani za bilo koji pojedini entitet. daje master sat igre, tako da svaki sistem čita istu "vreme od posljednjeg okvira" vrijednost. Res<Time> Explain Single<&mut Transform, With>? traži Bevy za tačno jedan entitet koji ima komponenta i takođe nosi Tagovi: The dio znači da namjeravamo da modificiramo tu transformaciju ili poziciju igrača (sjetite se, dodali smo transformaciju komponentu u funkciji podešavanja). Ako je postojao više od jednog igrača, ovaj ekstraktor bi se žalio, što je savršeno za igru jednog junaka. Single<&mut Transform, With<Player>> Transform Player &mut Transform What’s Vec2::ZERO? je dvodimenzionalni vektor sa obe vrednosti postavljene na nulu: Koristimo ga kao polazni pravac prije čitanja ulaza na tipkovnici. Vec2::ZERO Vec2 { x: 0.0, y: 0.0 } What’s this KeyCode::… pattern? U pitanju je ... su enumi (kasnije će se pokriti enumi) koji predstavljaju specifične ključeve na tastaturi. jednostavno pita Bevy da li je taj ključ zadržan tokom trenutnog okvira. KeyCode::ArrowLeft KeyCode::ArrowRight input.pressed(KeyCode::ArrowLeft) Ignorišemo smjer nule tako da igrač stoji kada se ne pritisnu ključevi. pretvara vektor u dužinu 1 tako da diagonalni pokret nije brži od ravnog pokreta. kaže koliko piksela u sekundi da se pomakne, i Vraća vrijeme kadra – broj sekundi od prethodnog kadra – tako da ih množenje daje udaljenost koju bismo trebali putovati ovom ažuriranjem. normalize() speed time.delta_secs() Registracija sistema pokreta // Update main function fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .add_systems(Update, move_player) // Line update alert .run(); } Why is move_player added as Update? What’s Update? pokreće svaki okvir, pa ga uključimo u Raspored za. je Bevy-ova ugrađena faza koja puca jednom po igri nakon što je pokretanje završeno. move_player Update Update So does systems mean functions we want bevy to execute at a particular time, like initial setup or on every game loop update? Točno. sistem je samo funkcija Rust koju dajete Bevy-u sa lepljivom notom koja kaže "trčite me tokom pokretanja" ili "trčite me na svakoj ispravci", a motor se dužno pokorava. Hajde da trčimo. Dodavanje Sprite grafike Mi ćemo koristiti univerzalni LPC SpriteSheet Generator da daju naš lik neke ličnosti. Možete remix telo dijelove, odjeću i boje na i izvoziti punu spritesheet. Ovaj link Za ovaj projekt spritesheet je već uključen u Spustite datoteke sa slikama u tako da ih Bevy može pronaći kada se igra pokrene. Morat ćete stvoriti Direktorija unutar src fascikle. Repo src/assets assets Reaktorski kod Moramo dodati više koda i dodavanje na main.rs će otežati čitanje i razumijevanje. ažurirajte svoju datoteku main.rs na sledeći način, takođe kreirajte drugu datoteku player.rs Putevi u Razdvajanje koda kao što je ovo olakšava rast projekta bez jedne velike datoteke koja radi sve. mod player; player.rs //main.rs use bevy::prelude::*; mod player; fn main() { App::new() .insert_resource(ClearColor(Color::WHITE)) // We have updated the bg color to white .add_plugins( DefaultPlugins.set(AssetPlugin { // Our assets live in `src/assets` for this project file_path: "src/assets".into(), ..default() }), ) .add_systems(Startup, setup_camera) .run(); } fn setup_camera(mut commands: Commands) { commands.spawn(Camera2d); } spušta globalnu postavku u aplikaciju tako da svaki okvir počinje sa belom pozadinom. .insert_resource(ClearColor(Color::WHITE)) je Bevy's loader za teksture, audio i druga sredstva. Izgleda da je unutra Upravo tamo žive naši spritovi. AssetPlugin file_path src/assets Izgradnja modula za igrače Sada kada imamo našu osnovnu strukturu aplikacija na mestu, vrijeme je da donesemo naš lik u život! Modul za rukovanje sa svime što je povezano s našim malim avanturistom.To održava naš kod organizovan i olakšava dodavanje više funkcija kasnije. player.rs Ovaj modul će držati sve konstante, komponente i sisteme koji omogućuju našem igraču da se kreće, animira i komunicira sa svetom. Dodajte sledeći kod u player.rs // player.rs use bevy::prelude::*; // Atlas constants const TILE_SIZE: u32 = 64; // 64x64 tiles const WALK_FRAMES: usize = 9; // 9 columns per walking row const MOVE_SPEED: f32 = 140.0; // pixels per second const ANIM_DT: f32 = 0.1; // seconds per frame (~10 FPS) #[derive(Component)] struct Player; // Moved from main.rs to here Te konstante daju imena brojevima koje ponovno koristimo za spritesheet i matematiku kretanja: veličina pločica, okviri po redovima, brzina hodanja i koliko brzo animacija napreduje. marker ovdje drži sve tipove specifične za igrača u jednom modulu. Player Definicija igračkih smerova // Append these lines of code to player.rs #[derive(Component, Debug, Clone, Copy, PartialEq, Eq)] enum Facing { Up, Left, Down, Right, } What’s an enum? Enum navodi nekoliko dozvoljenih vrednosti. Naš lik se uvijek suočava samo sa četiri smjera, tako da enum captures those options in one place. Facing Why can’t I use a struct here? Strukt bi trebao gomilu booleana kao U pitanju je , i morat ćete ih držati u sinhronizaciji. To komplikuje. Enum garantuje da ćete odabrati samo jedan pravac. facing_up:true facing_left:false When to decide between using enum and struct? Koristite strukturu kada vam je potrebno nekoliko polja, kao što je pozicija sa i , or player stats with health and stamina. Use an enum when you choose one option from a list, like which tool the player wields (sword, bow, wand) or which menu screen is active. x y Why the purpose of adding Debug, Clone, Copy, PartialEq, Eq macros? Neka nam štampaju lice za logove, and učiniti to trivijalno da duplira vrijednost, i / omogućiti kontrole jednakosti kada uspoređujemo smjernice. Debug Clone Copy PartialEq Eq Why can’t I copy through simple assignment, why should I add these macros? Podrazumevano, Rust pomera vrednosti umjesto da ih kopira, tako da kompilator dozvoljava da odaberete. i kao pomoćnik) kaže "to je jeftino, idite dalje i duplikujte ga." i Hajde da izravno uporedimo dva obraza, što je način na koji otkrijemo kada igrač promijeni pravac. Copy Clone PartialEq Eq What do you mean by rust moves values, instead of copying? Kada dodelite većinu vrednosti u Rust, stara varijabilna prestaje da je posjeduje ili, drugim riječima, umire. Obe varijable ostaju valjane. Copy Why does the old variable stop owning or die when assigned? Rust zahtijeva da svaka vrijednost ima jednog vlasnika tako da se memorija može sigurno osloboditi. This is a bit going over my head! Da, ne brinite, imamo još mnogo poglavlja i dok ih prolazite, mi ćemo se baviti ovim. Komponente animacionog sistema Kada imamo spritesheet s višestrukim okvirima (kao što je naša 9-frame šetnja animacija), potreban nam je način da kontrolišemo koliko brzo te okvire igraju. Razmislite o tome kao flipbook - ako previše polako okrenete stranice, animacija će izgledati prljavo. daje nam preciznu kontrolu nad ovim vremenom, osiguravajući da animacija našeg lika izgleda glatko i prirodno u pravoj brzini. AnimationTimer Uslovi korišćenja tako da svaki entitet igrača zna kada treba napredovati do sledećeg animacijskog okvira.Svaki tik predstavlja jedan dio vremena; jednom kada časovnik udari u svoj interval, mi se krećemo na sledeći sprite u listu. AnimationTimer Timer // Append these lines of code to player.rs #[derive(Component, Deref, DerefMut)] struct AnimationTimer(Timer); #[derive(Component)] struct AnimationState { facing: Facing, moving: bool, was_moving: bool, } What’s Deref, DerefMut macros doing? Neka naš zagrljaj pretvara se da je unutrašnji dok čitamo iz njega, i radi isto za pisma. To znači da možemo samo pozvati je bez ručnog izvlačenja unutrašnje vrednosti prvo. Deref Timer DerefMut timer.tick(time.delta()) AnimationTimer So we are renaming the Timer to AnimationTimer? Mi zavijamo časovnik, a ne ga preimenujemo. kao mala kutija koja drži , plus etiketa koja kaže "ovo pripada animaciji igrača." i stavite ga u tu kutiju, tako da svaki igrač može imati svoj vlastiti časovnik ako nam je potrebno više heroja. AnimationTimer Timer Timer So it’s an instance of AnimationTime? da, je tuple struktur koji sadrži a . We build one when we spawn the player so each entity can carry its own timer data. This pattern shows up whenever you want to attach extra meaning to an existing type without writing a brand-new API. AnimationTimer Timer pamti na koji način igrač pokazuje, da li se kreće i da li je upravo počeo ili zaustavio.Sistemi to čitaju kako bi odabrali animirane redove i ponovili okvire kada se pokret promeni. AnimationState Oslobađanje igrača Mi punimo spritesheet kroz , kreirajte raspored tekstura atlas tako da Bevy zna mrežu, i odaberite početni okvir za junaka koji gleda prema dolje. AssetServer // Append these lines of code to player.rs fn spawn_player( mut commands: Commands, asset_server: Res<AssetServer>, mut atlas_layouts: ResMut<Assets<TextureAtlasLayout>>, ) { // Load the spritesheet and build a grid layout: 64x64 tiles, 9 columns, 12 rows let texture = asset_server.load("male_spritesheet.png"); let layout = atlas_layouts.add(TextureAtlasLayout::from_grid( UVec2::splat(TILE_SIZE), WALK_FRAMES as u32, // columns used for walking frames 12, // at least 12 rows available None, None, )); // Start facing down (towards user), idle on first frame of that row let facing = Facing::Down; let start_index = atlas_index_for(facing, 0); commands.spawn(( Sprite::from_atlas_image( texture, TextureAtlas { layout, index: start_index, }, ), Transform::from_translation(Vec3::ZERO), Player, AnimationState { facing, moving: false, was_moving: false }, AnimationTimer(Timer::from_seconds(ANIM_DT, TimerMode::Repeating)), )); } postavlja polazni pravac i zastave da je lik je prazno sada i bio je prazno posljednji okvir. AnimationState { facing, moving: false, was_moving: false } creates a repeating stopwatch that fires every sekundi za unapređenje spritesheet. AnimationTimer(Timer::from_seconds(ANIM_DT, TimerMode::Repeating)) ANIM_DT ? What’s an AssetServer The je Bevy file loader i menadžer koji se bavi učitanjem i cachingom igranih sredstava kao što su slike, zvukovi i 3D modeli. AssetServer Kada zovete , to ne odmah učitava datoteku u memoriju. Umjesto toga, to vraća rukovanje koje možete koristiti kasnije. Ovo se zove "lazy loading" - stvarno učitavanje datoteke se događa u pozadini, a Bevy će vas obavijestiti kada je spremno. asset_server.load("path/to/sprite.png") Ovaj pristup je efikasan jer: Mnoge entitete mogu deliti isti sprit bez punjenja više puta Imovina se naplaćuje samo kada je stvarno potrebna Bevy može optimizovati i opterećivati imovinu za bolje performanse Ako imovina ne napuni, neće se srušiti cijela igra U našem slučaju, traži spritesheet i vraća ručku za praćenje statusa punjenja. asset_server.load("sprites/player.png") Sistem pokreta Sada kada imamo našeg igrača sa svim potrebnim komponentama, moramo ažurirati naš postojeći sistem pokreta kako bismo pratili u kom pravcu se igrač suočava.To je ključno za animacijski sistem da zna koji red spritesheet koristiti - svaki pravac (vrha, dolje, lijevo, desno) odgovara različitom redu u našem sprites atlasu. Ovaj ažurirani sistem će otkriti pravac kretanja i ažurirati prema tome, tako da naš animacijski sistem može odabrati ispravan sprite red za šetnje animacije u svakom pravcu. AnimationState // Append these lines of code to player.rs fn move_player( input: Res<ButtonInput<KeyCode>>, time: Res<Time>, mut player: Query<(&mut Transform, &mut AnimationState), With<Player>>, ) { let Ok((mut transform, mut anim)) = player.single_mut() else { return; }; let mut direction = Vec2::ZERO; if input.pressed(KeyCode::ArrowLeft) { direction.x -= 1.0; } if input.pressed(KeyCode::ArrowRight) { direction.x += 1.0; } if input.pressed(KeyCode::ArrowUp) { direction.y += 1.0; } if input.pressed(KeyCode::ArrowDown) { direction.y -= 1.0; } if direction != Vec2::ZERO { let delta = direction.normalize() * MOVE_SPEED * time.delta_secs(); transform.translation.x += delta.x; transform.translation.y += delta.y; anim.moving = true; // Update facing based on dominant direction if direction.x.abs() > direction.y.abs() { anim.facing = if direction.x > 0.0 { Facing::Right } else { Facing::Left }; } else { anim.facing = if direction.y > 0.0 { Facing::Up } else { Facing::Down }; } } else { anim.moving = false; } } Upita traži dokaz za pojedini entitet označen Omogućava mutibilni pristup njegovoj i Izgradimo smerni vektor iz pritisnutih ključeva, normalizujemo ga tako da dijagonalni ulaz nije brži, i pomaknemo igrača brzinom × vremenom okvira. logika suočavanja uspoređuje horizontalne i vertikalne snage kako bi se odlučilo na koji način bi sprite trebao izgledati. Player Transform AnimationState Implementacija animacije Sada imamo sve komade na mestu - naš igrač se može kretati u različitim pravcima, a mi pratimo na koji način se suočavaju. Ovaj sistem uzima informacije o smjeru iz našeg sistema pokreta i koristi animacijski časovnik da se krene kroz sprite okvire na pravu brzinu.Radi se složenom logikom prebacivanja između različitih animiranih redova kada igrač promeni smjer, i osigurava glatke prijelaze između hodanja i neaktivnih stanja. // Append these lines of code to player.rs fn animate_player( time: Res<Time>, mut query: Query<(&mut AnimationState, &mut AnimationTimer, &mut Sprite), With<Player>>, ) { let Ok((mut anim, mut timer, mut sprite)) = query.single_mut() else { return; }; let atlas = match sprite.texture_atlas.as_mut() { Some(a) => a, None => return, }; // Compute the target row and current position in the atlas (column/row within the 9-column row) let target_row = row_zero_based(anim.facing); let mut current_col = atlas.index % WALK_FRAMES; let mut current_row = atlas.index / WALK_FRAMES; // If the facing changed (or we weren't on a walking row), snap to the first frame of the target row if current_row != target_row { atlas.index = row_start_index(anim.facing); current_col = 0; current_row = target_row; timer.reset(); } let just_started = anim.moving && !anim.was_moving; let just_stopped = !anim.moving && anim.was_moving; if anim.moving { if just_started { // On tap or movement start, immediately advance one frame for visible feedback let row_start = row_start_index(anim.facing); let next_col = (current_col + 1) % WALK_FRAMES; atlas.index = row_start + next_col; // Restart the timer so the next advance uses a full interval timer.reset(); } else { // Continuous movement: advance based on timer cadence timer.tick(time.delta()); if timer.just_finished() { let row_start = row_start_index(anim.facing); let next_col = (current_col + 1) % WALK_FRAMES; atlas.index = row_start + next_col; } } } else if just_stopped { // Not moving: keep current frame to avoid snap. Reset timer on transition to idle. timer.reset(); } // Update previous movement state anim.was_moving = anim.moving; } // Returns the starting atlas index for the given facing row fn row_start_index(facing: Facing) -> usize { row_zero_based(facing) * WALK_FRAMES } fn atlas_index_for(facing: Facing, frame_in_row: usize) -> usize { row_start_index(facing) + frame_in_row.min(WALK_FRAMES - 1) } fn row_zero_based(facing: Facing) -> usize { match facing { Facing::Up => 8, Facing::Left => 9, Facing::Down => 10, Facing::Right => 11, } } oboje provjerava rezultat i naziva potrebne komade. Ako upit uspe, kod se veže U pitanju je i tako da ih možemo koristiti kasnije. Ako ne uspije (bez igrača, ili više od jednog), udarimo na Branka i izlazak odmah. Rust koristi Tip za ovo: means “query returned exactly one result,” znači "nešto o tom upitu nije odgovaralo". let Ok((mut anim, mut timer, mut sprite)) = query.single_mut() else { return; }; anim timer sprite else Result Ok Err Nakon toga mi On je , što je Rustov tip „možda postoji vrijednost“. znači da atlas teksture postoji i možemo ga prilagoditi; znači da se još nije učitala, pa preskočimo i dozvolimo da sledeći okvir pokuša ponovo. To je isti uzorak koji biste koristili kada biste provjerili mapu ili keš: koristite samo vrijednost kada pretraga vrati nešto. match Option Some(atlas) None povlači stanje animacije, časovnik i sprite rukovanje za igrača. On izračunava koji red atlas odgovara trenutnom obrazu, snaps na taj red kada se smjer mijenja, i koristi časovnik da pređe kroz kolone u stabilnom tempu. Kada se kretanje zaustavi mi ponovimo časovnik tako da animacija leži na poslednjem prikazanom okviru. animate_player Kreiranje Plugin igrača // Append these lines of code to player.rs pub struct PlayerPlugin; impl Plugin for PlayerPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, spawn_player) .add_systems(Update, (move_player, animate_player)); } } u Bevy, plugin je samo struktur koji zna kako da registruje sisteme, resurse i sredstva. za , dajemo Bevy kontrolnu listu: kad god se ovaj plugin dodaje aplikaciji, pokrenite kod unutar njega da biste postavili sve što funkcija igrača treba. od postaje zagrljaj igrača-specifičnih poziva. PlayerPlugin Plugin PlayerPlugin main.rs Naši Metoda je kontrolna lista. Bevy nam daje mutable , a mi smo bolt na sistemima koje nam je stalo. Planirano je u tako da se sprite pojavljuje čim se igra pokrene. i Uđite u raspored tako da izvršavaju svaki okvir – rukovanje ulazom i animacijom u lockstep. u automatski pokreće čitav protok igrača. build App spawn_player Startup move_player animate_player Update PlayerPlugin App::new() So this build function is an in-built structure, which I have to write? Da, to je Trait kaže: “Svaki plugin mora pružiti Mi implementiramo tu osobinu, tako da Rust očekuje od nas da opskrbimo tijelo. Bevy zove ovu metodu kada učita plugin, zbog čega dodajemo sve naše sisteme unutar njega. Plugin build(&self, app: &mut App) What’s a trait? Značajka je ugovor koji opisuje koje metode tip mora pružiti. Trait kaže “daj mi a funkciju tako da mogu da registrujem vaše sustave.“ Primjenom te osobine za , mi smo pričvrstili u proces pokretanja Bevy-a i ubrizgali vlastiti konfiguracijski kod. ponaša se kao i svaki drugi Bevy plugin, ali instalira naše sisteme specifične za igrače. Plugin build PlayerPlugin PlayerPlugin Konačna integracija // Update the main function in main.rs use crate::player::PlayerPlugin; fn main() { App::new() .insert_resource(ClearColor(Color::WHITE)) .add_plugins( DefaultPlugins.set(AssetPlugin { file_path: "src/assets".into(), ..default() }), ) .add_systems(Startup, setup_camera) .add_plugins(PlayerPlugin) // Update this line .run(); } Hajde da trčimo. cargo run Ovaj članak je izvorno objavljen na mom blogu. Ovaj članak je izvorno objavljen na mom blogu. Još poglavlja koje dolaze, ostanite usklađeni.Povežite se sa mnom na LinkedInu, pratite me na X za najnovije ažuriranja. Linkovi X