Dit is hoofdstuk 1 van mijn Rust-Bevy-tutorialserie "The Impatient Programmer's Guide to Bevy and Rust: Build a Video Game from Scratch". Word lid van onze community om op de hoogte te zijn van nieuwe releases in deze serie. Word lid van onze gemeenschap Beschikbaar hier At the end of this chapter, you’ll be able to achieve the results in the GIF below: Instructies Setup instellen Als u niet hebt ingesteld Maar alsjeblieft, volg de Om het op te zetten. rust Officiële gids Create a fresh project: cargo new bevy_game cd bevy_game Open and add Bevy: Cargo.toml [dependencies] bevy = "0.16.1" Ik ga ervan uitgaan dat je ervaring hebt met programmeren in één taal zoals javascript of python. Denken in systemen Wat hebben we nodig om een eenvoudig spel te bouwen waarmee de speler van de toetsenbordinvoer kan verhuizen? World System: Maak een spelwereld waar de speler kan bewegen. Input System: Monitor toetsenbordinvoer en vertaal het in spelerbeweging. We zien het bovenstaande patroon zeer gebruikelijk in het bouwen van games, het patroon van creëren / opzetten en het patroon van het bewaken van game-state veranderingen en het bijwerken van de spelwereld entiteiten. Setup: Spaan vijand met verschillende gedragingen, laden vijandelijke sprites en animaties, configureren vijandelijke gezondheid en schade waarden. Update: verplaats vijanden naar de speler, controleer of vijanden worden geraakt door kogels, verwijder dode vijanden en genereer nieuwe vijanden op intervallen. In de kern van Bevy hebben we dit eenvoudige Dit fundamentele patroon maakt Bevy zowel krachtig als gemakkelijk te begrijpen - zodra je dit concept begrijpt, wordt het bouwen met bevy makkelijker. setup and update system Stel je een functie voor genaamd setup, die je "opdrachten" geeft als een argument, en het geeft je de mogelijkheid om alles te creëren of te creëren wat je wilt. // Pseudo code, doesn't compile fn setup(mut commands: Commands) { // Create anything you want commands.spawn() } What’s mut? In Rust moeten we expliciet vermelden dat we van plan zijn deze waarde te wijzigen. Standaard behandelt Rust aangegeven waarden als alleen gelezen. zegt Rust dat we van plan zijn de waarde te wijzigen. Rust gebruikt deze kennis om een hele klasse van bugs te voorkomen. mut mut De ? And what’s this mut commands: Commands Het komt uit de bevy-bibliotheek, waarmee je dingen aan je spelwereld kunt toevoegen. laat de compilator weten dat deze parameter de opdrachtinterface van Bevy is. Skip de hint en Rust kan niet zeker zijn wat er verschijnt, dus het blokkeert de build. :Commands What’s a type? Een type vertelt Rust met welk type waarde u omgaat – getallen, tekst, timers, Bevy-opdrachten, enzovoort. Isn’t this too much work? Het voorkomt fouten en helpt u met prestaties, bijvoorbeeld: als u probeert een string aan een getal toe te voegen, stopt de compilator u voordat het spel loopt. a is slechts twee getallen, dus Rust opslaat precies twee getallen, geen verrassing extra ruimte, die dingen snel houdt als je duizenden spelentiteiten hebt. Vec2 Wanneer u een Vec2-object in JavaScript of Python maakt, slaat u niet alleen twee getallen op. De runtime voegt type metadata, eigenschapsinformatie en prototype-referenties toe - waardoor uw eenvoudige 8-byte gegevensstructuur in ~48 bytes geheugen wordt omgezet. Het type-systeem van Rust werkt op compilatietijd.Wanneer u een Vec2 struct met twee f32-velden verklaart, is dat precies wat wordt opgeslagen - slechts 8 bytes, geen extra metagegevens.De compilator kent de typen al, dus is er geen runtime-type-informatie nodig. Met 1000 spelentiteiten wordt dit verschil dramatisch: Language Memory Usage Overhead Rust 8KB None Dynamic language ~48KB+ 6x overhead rust 8Kb geen Dynamische taal • 48 kB + 6x bovenop Dit gaat niet alleen over geheugengebruik - het gaat om prestaties. kleinere, voorspelbare geheugenopstellingen betekenen een beter gebruik van de CPU-cache, wat rechtstreeks leidt tot snellere frame rates in games waar je duizenden entiteiten per frame verwerkt. Een camera opzetten What should we setup first? We hebben een camera nodig omdat niets op het scherm zonder één wordt weergegeven.De wereld kan bestaan in gegevens, maar de camera bepaalt wat er daadwerkelijk wordt getrokken. //Pseudo code, don't use this yet fn setup(mut commands: Commands) { commands.spawn(Camera2d) } What’s Camera2d? is Bevy's ingebouwde 2D-camera bundel. Spaan het en je krijgt een ready-to-go-zicht van je 2D-scène. Camera2d What’s a bundle? Een bundel is slechts een groep componenten die je vaak samenvoegt.Bevy schept bijvoorbeeld een dat de positie, textuur, kleurschaduw en zichtbaarheid verpakt; het voortbrengen van die bundel geeft een sprite-entiteit in één oproep. SpriteBundle Registreer onze setupfunctie We moeten onze setupfunctie registreren met bevy om te worden geactiveerd op Startup. Update uw met de volgende code. 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? is als een starter kit, dingen die je constant bereikt bij het bouwen van een spel - - het , componenten, wiskundige helpers, enzovoort bevy::prelude App Commands What’s App::new()…? het maken van de game-applicatie, loads rendering, input, audio en andere systemen, Registreer onze startfunctie en Handen controleren naar de hoofdloop van Bevy. App::new() add_plugins(DefaultPlugins) add_systems(Startup, setup) run() Why register a startup function? What does it do? We gebruiken ze om camera's, spelers en andere dingen op te zetten die moeten bestaan zodra het venster wordt geopend. What’s bevy main loop? Na het opstarten gaat Bevy in zijn hoofdloop: het onderzoekt input, voert uw systemen uit, actualiseert de wereld en maakt een frame. Laten we het runnen cargo run Ja, we hebben alleen de camera ingesteld, nu laten we onze speler toevoegen. Het opzetten van de speler Laten we nu onze speler creëren! en Welnu, in Bevy is alles een met Met die systemen kun je werken. Setup System Update System Entity Components Denk aan een entiteit als een unieke ID (zoals een socialezekerheidsnummer) en componenten als de bijgevoegde gegevens.Voor onze speler hebben we componenten nodig zoals bewegingssnelheid, gezondheid of speciale vaardigheden. . struct is een van de kern bouwstenen van roest. Het groept soortgelijke gegevens samen. struct zodat het type zelf fungeert als een tag die we aan de spelerentiteit kunnen hechten. struct Player // Place this before main function in main.rs #[derive(Component)] struct Player; Why tag? Tag markeert een entiteit voor latere lookup. omdat is alleen aan onze held gehecht, kunnen systemen Bevy vragen, "geef me de entiteit met de Player-tag" en werken met die ene. Player What’s this #[derive(component)]? Een macro is de manier waarop Rust voor u een vooraf gedefinieerde sjablooncode genereert. automatisch de boilerplaat Bevy nodig injecteert, zodat het kan opslaan en vinden entiteiten, waardoor we niet overal dezelfde lijmcode kunnen kopiëren.We zullen makro's later in de serie nader bekijken.Dit is het moment waarop ons speltype een onderdeel wordt. derive #[derive(Component)] Player What’s a component, and why should the player be a component? Een component is een stuk gegevens dat aan een entiteit is gehecht. Positie, snelheid, gezondheid en zelfs het idee van "dit is de speler" leven allemaal in componenten. Een component die we later kunnen aanvragen voor die entiteit, meer componenten toevoegen (zoals gezondheid of inventaris) en laten de systemen van Bevy precies de entiteiten kiezen die ze moeten bijwerken. Player Voorlopig zullen we ons personage op het scherm vertegenwoordigen met behulp van het "@" -symbool. // 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, )); } neemt die tuple en behandelt het als een bundel componenten. Deze enkele oproep voegt de tekst toe die we willen tonen, de lettertype-instellingen, de kleur, de positie en de Een tag die de entiteit identificeert. commands.spawn(( ... )) Player What’s a tuple? Een tuple is een geordende lijst met waarden die in parantezen zijn geschreven. Rust volgt elke positie, dus houd twee waarden naast elkaar zonder dat u een struct hoeft te creëren. (Text2d::new("@"), TextColor(Color::WHITE)) Whats an entity? Een entiteit is de unieke ID die Bevy gebruikt om componenten bij elkaar te koppelen. Op zich heeft het geen gegevens, maar zodra je componenten aan het koppelt, vertegenwoordigt die ID iets in je spelwereld. Elke bundel produceert een nieuwe entiteit met een unieke ID en de vermelde componenten, die je als volgt kunt voorstellen: Entity Components it carries #42 Camera2d #43 , , , , Text2d("@") TextFont TextColor Transform Player #42 Camera2d # 43 - het , , - het Text2d("@") TextFont TextColor Transform Player Zodra de wachtrij is gespoeld, leven die entiteiten in de wereld, klaar voor systemen om ze te ontdekken door de tags (componenten) die ze dragen. De implementatie van de spelersbeweging Laten we nu onze Overweeg wat we nodig hebben om een speler te verplaatsen: Update System Keyboard input - om te weten welke toetsen worden ingedrukt Tijd - om de beweging soepel te maken, ongeacht de frame rate Spelerpositie - om de speler daadwerkelijk te verplaatsen In other game engines, you’d spend time manually connecting these systems together. But here’s the magic of Bevy - you just ask for what you need in your function parameters, and Bevy automatically provides it! Laten we onze bewegende spelerfunctie schrijven. // 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 of Resources zijn stukken game-wide informatie die niet zijn gekoppeld aan een enkele entiteit. geeft u de meesterklok van het spel, zodat elk systeem dezelfde "tijd sinds het laatste frame" waarde leest. Res<Time> Explain Single<&mut Transform, With>? asks Bevy for exactly one entity that has a component en draagt ook de Tag. de deel betekent dat we van plan zijn om die transform of speler positie te wijzigen (Vergeet niet, we hebben transform component toegevoegd in de configuratie functie). Single<&mut Transform, With<Player>> Transform Player &mut Transform What’s Vec2::ZERO? is een tweedimensionale vector met beide waarden ingesteld op nul: We gebruiken het als de startrichting voordat we de toetsenbordinvoer lezen. Vec2::ZERO Vec2 { x: 0.0, y: 0.0 } What’s this KeyCode::… pattern? - het ... zijn enums (will cover enums later) die specifieke toetsen op het toetsenbord vertegenwoordigen. Bevy vraagt gewoon of die sleutel gedurende het huidige frame vastgehouden wordt. KeyCode::ArrowLeft KeyCode::ArrowRight input.pressed(KeyCode::ArrowLeft) We negeren de nulrichting, zodat de speler stilstaat als er geen toetsen worden ingedrukt. Converteert de vector naar lengte 1, zodat diagonale beweging niet sneller is dan rechte beweging. hoeveel pixels per seconde te bewegen, en returns the frame time—the number of seconds since the previous frame—so multiplying them gives the distance we should travel this update. Finally we add that delta to the player’s transform translation to move the sprite on screen. normalize() speed time.delta_secs() Registering the Movement System // 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? elke frame draait, dus we pluggen het in de Het schema. is Bevy's ingebouwde fase die één keer per game loop brandt nadat de start is voltooid. 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? Een systeem is gewoon een Rust-functie die je aan Bevy geeft met een kleverige opmerking die zegt "run me during Startup" of "execute me on every Update", en de motor gehoorzaamt. Laten we het runnen. Voeg Sprite Graphics toe We’ll use the Universal LPC SpriteSheet Generator to give our character some personality. You can remix body parts, clothes, and colors at en een volledige spritesheet exporteren. Deze link Voor dit project is de spritesheet al opgenomen in de Gooi de opgegeven beeldbestanden in zodat Bevy ze kan vinden wanneer het spel loopt. Directory binnen de src-map. Repo src/assets assets Refactoren code We moeten meer code toevoegen en toevoegen aan main.rs zal het moeilijk maken om te lezen en te begrijpen. Update your main.rs file to the following, also create another file player.rs Pulls in de Het splitsen van code zoals dit maakt het gemakkelijker om het project te laten groeien zonder dat één groot bestand alles doet. 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); } drops a global setting into the app so every frame starts with a white background. .insert_resource(ClearColor(Color::WHITE)) is de lader van Bevy voor texturen, audio en andere activa. so it looks inside Dat is waar onze sprites wonen. AssetPlugin file_path src/assets Het opbouwen van de Player Module Nu we onze basisappstructuur hebben, is het tijd om onze personage tot leven te brengen! Module om alles te verwerken met betrekking tot onze kleine avonturier.Dit houdt onze code georganiseerd en maakt het gemakkelijker om later meer functies toe te voegen. player.rs Deze module zal alle constanten, componenten en systemen bevatten die onze speler bewegen, animeren en interacteren met de wereld. Voeg de volgende code toe in 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 Deze constanten geven namen aan de getallen die we hergebruiken voor de spritesheet en bewegingsmatheet: tegelgrootte, frames per rij, loopsnelheid en hoe snel de animatie vordert. marker houdt hier alle speler-specifieke typen in één module. Player Definieer de spelersrichtingen // 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? Een enum vermeldt een handvol toegestane waarden. Ons personage kijkt altijd slechts in vier richtingen, dus de Enum vangt deze opties op één plek. Facing Why can’t I use a struct here? Een struct zou een hoop booleanen nodig hebben zoals - het , en je zou ze moeten synchroniseren. dat maakt het ingewikkeld. Enum garandeert dat je maar één richting kiest. facing_up:true facing_left:false When to decide between using enum and struct? Gebruik een struct wanneer u meerdere velden nodig hebt, zoals een positie met en , of spelersstatistieken met gezondheid en uithoudingsvermogen Gebruik een enum wanneer u een optie uit een lijst kiest, zoals welk instrument de speler gebruikt (zwaard, boog, wand) of welk menu-scherm actief is. x y Why the purpose of adding Debug, Clone, Copy, PartialEq, Eq macros? Laat ons de facing voor logs afdrukken, en het triviaal maken om de waarde te dupliceren, en / Vergelijkingscontroles kunnen worden toegestaan bij het vergelijken van richtingen. Debug Clone Copy PartialEq Eq Why can’t I copy through simple assignment, why should I add these macros? Standaard verplaatst Rust waarden in plaats van ze te kopiëren, dus de compilator maakt u opt-in. (en als een helper) zegt "het is goedkoop, ga verder en dubbel het." en Laten we twee facetten direct vergelijken, dat is hoe we detecteren wanneer de speler de richting verandert. Copy Clone PartialEq Eq What do you mean by rust moves values, instead of copying? Wanneer u de meeste waarden in Rust toewijst, stopt de oude variabele met het bezit ervan of sterft ze met andere woorden. behoudt beide variabelen geldig. Copy Why does the old variable stop owning or die when assigned? Rust zorgt ervoor dat elke waarde een enkele eigenaar heeft, zodat geheugen veilig kan worden vrijgegeven. This is a bit going over my head! Ja, maak je geen zorgen, we hebben nog veel meer hoofdstukken te gaan en als je er doorheen gaat, zullen we dit aanpakken. Animatiesysteem componenten Wanneer we een spritesheet hebben met meerdere frames (zoals onze 9-frame wandelen animatie), hebben we een manier nodig om te controleren hoe snel die frames spelen. Denk aan het als een flipbook - als je de pagina's te langzaam draait, ziet de animatie er onhandig uit. geeft ons nauwkeurige controle over deze timing, waardoor de loopanimatie van onze personage er soepel en natuurlijk uitziet op de juiste snelheid. AnimationTimer Wraps een bewijs Elke tik vertegenwoordigt een stukje tijd; zodra de timer zijn interval treft, gaan we naar de volgende sprite in het blad. 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? lets our wrapper pretend to be the inner Als we erover lezen, en hetzelfde voor schrijvers. dat betekent dat we alleen maar kunnen bellen zijn zonder eerst de interne waarde handmatig uit te trekken. Deref Timer DerefMut timer.tick(time.delta()) AnimationTimer So we are renaming the Timer to AnimationTimer? We wikkelen de timer, niet hernoemen. Als een kleine doos die een , plus een etiket dat zegt "dit behoort tot de speler animatie." en steek het in die doos, zodat elke speler zijn eigen timer kon hebben als we meerdere helden nodig hadden. AnimationTimer Timer Timer So it’s an instance of AnimationTime? Ja wel, is een tuple struct die een We bouwen er een wanneer we de spawn van de speler maken, zodat elke entiteit zijn eigen timergegevens kan dragen.Dit patroon verschijnt telkens wanneer u extra betekenis wilt toevoegen aan een bestaand type zonder een gloednieuwe API te schrijven. AnimationTimer Timer herinnert zich op welke manier de speler wijst, of ze zich bewegen en of ze net zijn gestart of gestopt.Systemen lezen dit om animatielijnen te selecteren en frames te resetten wanneer beweging verandert. AnimationState Spelen met de speler We laden de spritesheet door de , creëer een textuur-atlas lay-out zodat Bevy het raster kent, en kies het startframe voor een held die naar beneden kijkt. 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)), )); } stelt de startrichting en vlaggen dat het personage is leeg op dit moment en was leeg laatste frame. AnimationState { facing, moving: false, was_moving: false } creëert een herhaalde stopwatch die elke seconden om de spritesheet vooruit te zetten. AnimationTimer(Timer::from_seconds(ANIM_DT, TimerMode::Repeating)) ANIM_DT ? What’s an AssetServer De is Bevy's bestandslader en -beheerder die het laden en cachen van game-activa zoals afbeeldingen, geluiden en 3D-modellen verwerkt. AssetServer Wanneer je belt , het laadt het bestand niet onmiddellijk in het geheugen. In plaats daarvan retourneert het een handleiding die u later kunt gebruiken. Dit wordt "lazy loading" genoemd - het werkelijke bestandloaden gebeurt in de achtergrond, en Bevy zal u op de hoogte stellen wanneer het klaar is. asset_server.load("path/to/sprite.png") Deze aanpak is effectief omdat: Meerdere entiteiten kunnen dezelfde sprite delen zonder het meerdere keren te laden Activa worden alleen geladen wanneer ze daadwerkelijk nodig zijn Bevy kan assets optimaliseren en batch-load voor betere prestaties If an asset fails to load, it won’t crash your entire game In our case, vraagt het spritesheet en retourneert een handleiding om de laadstatus te volgen. asset_server.load("sprites/player.png") bewegingssysteem Nu we onze speler met alle benodigde componenten hebben gebaard, moeten we ons bestaande bewegingssysteem bijwerken om de richting te volgen waarmee de speler wordt geconfronteerd.Dit is cruciaal voor het animatiesysteem om te weten welke rij van de spritesheet te gebruiken - elke richting (op, naar beneden, links, rechts) komt overeen met een andere rij in onze sprite-atlas. Dit bijgewerkte systeem zal de bewegingsrichting detecteren en de dienovereenkomstig, zodat ons animatiesysteem de juiste sprite rij kan kiezen voor wandelen animaties in elke richting. 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; } } De query vraagt om bewijs voor de ene entiteit Het geven van een muteerbare toegang tot de en We bouwen een richtingsvector uit de ingedrukte toetsen, normaliseren het zodat diagonale invoer niet sneller is, en bewegen de speler door snelheid × frame tijd.De facing logica vergelijkt horizontale versus verticale kracht om te beslissen op welke manier de sprite eruit zou moeten zien. Player Transform AnimationState Animatie implementatie Nu hebben we alle stukken op hun plaats - onze speler kan in verschillende richtingen bewegen, en we volgen op welke manier ze worden geconfronteerd. Dit systeem neemt de richtsinformatie van ons bewegingssysteem en gebruikt de animatietimer om door de sprite frames met de juiste snelheid te fietsen.Het behandelt de complexe logica van het schakelen tussen verschillende animatielijnen wanneer de speler de richting verandert, en zorgt voor soepele overgangen tussen wandelen en stilstand. // 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, } } beide controleert het resultaat en noemt de stukken die we nodig hebben. als de query lukt, bindt de code - het en dan kunnen we ze later gebruiken. als het mislukt (geen speler, of meer dan één), slaan we de Branch en exit onmiddellijk. Rust gebruikt de Type voor dit: betekent "query retourneerde precies één resultaat", betekent "iets over die query niet overeenkwam." let Ok((mut anim, mut timer, mut sprite)) = query.single_mut() else { return; }; anim timer sprite else Result Ok Err Daarna hebben wij is een , dat is Rust's "misschien is er een waarde" type. betekent dat de textuuratlas bestaat en we kunnen het aanpassen; Het is hetzelfde patroon dat u zou gebruiken bij het controleren van een kaart of cache: gebruik alleen de waarde wanneer de zoekopdracht iets retourneert. match Option Some(atlas) None trekt de animatie staat, timer, en sprite handgreep voor de speler. Het berekent welke rij van de atlas overeenkomt met de huidige facing, snaps naar die rij wanneer de richting verandert, en gebruikt de timer om door kolommen te stappen in een stabiel tempo. Wanneer beweging stopt we de timer opnieuw instellen zodat de animatie rust op de laatste weergegeven frame. animate_player Het creëren van de Player Plugin // 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)); } } is onze “player module” in plug-in vorm. In Bevy is een plugin gewoon een struct die weet hoe systemen, middelen en activa te registreren. voor , geven we Bevy een checklist: telkens wanneer deze plugin wordt toegevoegd aan de app, voer de code in om alles in te stellen wat de spelerfunctie nodig heeft. van een tangle van speler-specifieke oproepen. PlayerPlugin Plugin PlayerPlugin main.rs De methode is de checklist. Bevy geeft ons een mutable , en we bolten op de systemen waar we om geven. Het is gepland in Dus de sprite verschijnt zodra het spel wordt gelanceerd. en Ga naar de plannen, zodat ze elk frame uitvoeren – invoer en animatie in lockstep verwerken. in Hierdoor wordt de volledige spelerstroom automatisch opgehaald. 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? Ja, de Treat zegt: “Elke plugin moet een We implementeren dat kenmerk, dus Rust verwacht van ons om het lichaam te leveren. Bevy noemt deze methode wanneer het de plug-in laadt, daarom voegen we al onze systemen erin toe. Plugin build(&self, app: &mut App) What’s a trait? Een eigenschap is een contract waarin wordt beschreven welke methoden een type moet bieden. Trait zegt: “Geef mij een functioneren, zodat ik uw systemen kan registreren.” , we verbinden ons met het startproces van Bevy en injecteren onze eigen setupcode. kenmerken laten verschillende types het gedrag delen, gedraagt zich net als elke andere Bevy-plugin, maar het installeert onze speler-specifieke systemen. Plugin build PlayerPlugin PlayerPlugin Finale integratie // 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(); } Laten we het runnen. cargo run Dit artikel werd oorspronkelijk gepubliceerd op mijn blog. Dit artikel werd oorspronkelijk gepubliceerd op . Mijn blog Neem contact met me op LinkedIn, volg me op X voor de laatste updates. Linkedin X