Ова е поглавје 1 од мојата Rust-Bevy упатствена серија "The Impatient Programmer's Guide to Bevy and Rust: Build a Video Game from Scratch". Придружете се на нашата заедница за да бидете ажурирани за новите изданија во оваа серија. Придружете се на нашата заедница Достапно тука At the end of this chapter, you’ll be able to achieve the results in the GIF below: Инструкции Setup Поставување Ако не сте го поставиле Сепак, ве молиме следете го За да го поставите. Почивка Официјален водич Create a fresh project: cargo new bevy_game cd bevy_game Open and add Bevy: Cargo.toml [dependencies] bevy = "0.16.1" Ќе претпоставиме дека имате искуство со програмирање на било кој јазик како JavaScript или Python. Размислување во системи Што ни е потребно за да изградиме едноставна игра која му овозможува на играчот да се движи од влезот на тастатурата? Светски систем: Креирајте игра свет каде што играчот може да се движи. Систем за влез: Следете го влезот на тастатурата и да го преведете во движење на играчот. Горенаведениот модел е многу честа појава во градењето на игри, моделот на создавање / поставување и моделот на следење на промените во состојбата на играта и ажурирање на ентитетите во играта. Поставување: Превртете го непријателот со различни однесувања, преземете непријателски шпритови и анимации, конфигурирајте го здравјето на непријателот и вредноста на штетата. Ажурирајте ги непријателите кон играчот, проверете дали непријателите се погодени од куршуми, отстранете мртви непријатели и создадете нови непријатели на интервали. Во срцето на Bevy, имаме оваа едноставна Овој основен модел е она што го прави Bevy моќен и лесен за разбирање - откако ќе го сфатите овој концепт, градењето со bevy станува полесно. setup and update system Замислете функција наречена подесување, која ви дава "команди" како аргумент, и ви дава способност да создадете или да создадете што сакате. // Pseudo code, doesn't compile fn setup(mut commands: Commands) { // Create anything you want commands.spawn() } What’s mut? Во Rust мораме експлицитно да споменеме дека планираме да ја смениме оваа вредност. Rust го користи ова знаење за да спречи цела класа на грешки. mut mut Ѕидот ? And what’s this mut commands: Commands Тоа е од bevy библиотека, која ви овозможува да додадете работи во вашиот игри светот. дозволувајќи му на компилаторот да знае дека овој параметар е командниот интерфејс на Bevy. Прескокнете го навестувањето и Rust не може да биде сигурен што се појавува, па го блокира градењето. :Commands What’s a type? Типот му кажува на Rust каков вид на вредност се справувате – броеви, текст, тајмери, команди Bevy и така натаму.Откако компилаторот го знае типот, може да провери дали секоја операција што ја изведувате на него навистина има смисла. Isn’t this too much work? Тоа ги спречува грешките и ви помага со перформансите, на пример: Ако се обидете да додадете низа на број, компилаторот ќе ве запре пред да се кандидира играта. А е само два броја, па Rust чува точно два броја, без изненадување дополнителен простор, кој ги одржува работите брзо кога имате илјадници игра ентитети. Vec2 Кога креирате Vec2 објект во JavaScript или Python, вие не само што складирате два броја. Времето за извршување додава метаподатоци за типот, информации за својствата и референци за прототипот - претворајќи ја вашата едноставна 8-битна структура на податоци во ~48 байти меморија. Системот за тип на Rust работи во време на компилација. Кога ќе прогласите Vec2 струк со две f32 полиња, тоа е токму она што се чува - само 8 байти, без дополнителни метаподатоци. Со 1.000 играчки ентитети, оваа разлика станува драматична: Language Memory Usage Overhead Rust 8KB None Dynamic language ~48KB+ 6x overhead Почивка 8 КБ никој Динамичен јазик од 48 KB+ 6x над главата Ова не е само за користење на меморијата - тоа е за перформанси. Помали, предвидливи распореди на меморијата значат подобра употреба на кеш процесорот, што директно се пренесува на побрзи стапки на рамки во игри каде што обработувате илјадници ентитети секој рамка. Поставување на камера What should we setup first? Потребна ни е камера, бидејќи ништо не се прикажува на екранот без тоа.Светот може да постои во податоци, но камерата одлучува што всушност се црта. //Pseudo code, don't use this yet fn setup(mut commands: Commands) { commands.spawn(Camera2d) } What’s Camera2d? е вграден пакет од 2D камери на Bevy. Спојте го и ќе добиете подготвен поглед на вашата 2D сцена. Camera2d What’s a bundle? Бунт е само група на компоненти што често ги спојувате заедно. На пример, Bevy бродови која пакува позиција, текстура, боја и видливост; раѓањето на тој пакет дава sprite ентитет во еден повик. SpriteBundle Регистрирање на нашата функција за поставување Треба да ја регистрираме нашата функција за поставување со bevy за да се активира на Startup. Ажурирање на Вашиот со следниот код. 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? е како стартер комплет, работи што постојано ги достигнувате кога градите игра - , на , компоненти, математички помагачи, итн bevy::prelude App Commands What’s App::new()…? создава апликација за игра, рендерирање на товар, влез, аудио и други системи, регистрира нашата функција за стартување, и контрола на рацете до главниот круг на Bevy. App::new() add_plugins(DefaultPlugins) add_systems(Startup, setup) run() Why register a startup function? What does it do? Системите за стартување работат еднаш пред првата рамка.Ние ги користиме за да поставиме камери, плејери и други работи кои треба да постојат веднаш штом се отвори прозорецот. What’s bevy main loop? По стартувањето, Bevy влегува во својот главен круг: го истражува внесот, ги изведува вашите системи, го ажурира светот и рендерира рамка. Да ја трчаме cargo run Да, ние само го поставивме камерата, сега да го додадеме нашиот играч. Поставување на играчот Сега да го создадеме нашиот играч! и Па, во Беви, сè е со Овие системи можат да работат. Setup System Update System Entity Components Размислете за ентитет како уникатен идентификатор (како број на социјално осигурување) и компоненти како податоци поврзани со него.За нашиот играч, ни се потребни компоненти како брзина на движење, здравје или специјални способности. . struct е еден од главните градежни блокови на ѓубриво. Тоа групира слични податоци заедно. струк така што самиот тип дејствува како таг што можеме да го прикачиме на ентитетот на играчот. struct Player // Place this before main function in main.rs #[derive(Component)] struct Player; Why tag? Таг означува ентитет за подоцнежен преглед. Системите можат да го прашаат Беви, „дај ми ентитет со таг на играч“ и да работат со само тој. Player What’s this #[derive(component)]? Кажува на Rust да го прикачи кодот на макросот на компонентата на оваа структура. Макросот е начин на Rust да генерира претходно дефиниран шаблон за вас. автоматски ја вбризгува потребната плоча за котел Bevy, така што може да се складира и да се најде субјекти, ни штеди од копирање на истиот лепак код насекаде. Ние ќе се погледне поблиску на макроси подоцна во серијата. derive #[derive(Component)] Player What’s a component, and why should the player be a component? Компонента е дел од податоците поврзани со ентитет. Позицијата, брзината, здравјето, па дури и идејата за „ова е играчот“ сите живеат во компоненти. Подоцна можеме да побараме компонента за тој ентитет, да додадеме повеќе компоненти (како што се здравјето или инвентар), и да им дозволиме на системите на Беви да ги изберат токму ентитетите што треба да ги ажурираат. Player Засега ќе го претставуваме нашиот карактер на екранот со помош на симболот "@". // 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, )); } го зема тој табел и го третира како збир компоненти. Овој еден повик го додава текстот што сакаме да го прикажеме, неговите поставки за фонт, бојата, позицијата и таг кој ја идентификува субјектот. commands.spawn(( ... )) Player What’s a tuple? A tuple е нареден список на вредности напишани во патенти. Rust ги следи секоја позиција, така што држи две вредности рамо до рамо без да треба да создаде структура. (Text2d::new("@"), TextColor(Color::WHITE)) Whats an entity? Еден ентитет е уникатен идентификатор кој Bevy го користи за да ги поврзе компонентите заедно. Самиот тој нема податоци, но откако ќе ги поврзете компонентите со него, тој идентификатор претставува нешто во вашиот свет на игри. Секој пакет произведува нова ентитет со уникатен ИД и наведените компоненти, кои можете да ги прикажете вака: Entity Components it carries #42 Camera2d #43 , , , , Text2d("@") TextFont TextColor Transform Player #42 Camera2d # 43 , на , на , на , на Text2d("@") TextFont TextColor Transform Player Откако редот ќе се измие, тие суштества живеат во светот, подготвени за системи да ги откријат според ознаките (компонентите) што ги носат. Имплементација на играчкото движење Ајде да ја создадеме нашата Размислете за она што ни е потребно за да го преместиме играчот: Update System Внесување на тастатура - да се знае кои клучеви се притискаат Време - за да се направи движење глатко без оглед на брзината на рамката Позиција на играчот - за да го преместите играчот Во други игри мотори, ќе потрошите време рачно поврзување на овие системи заедно. но тука е магијата на Bevy - само побарајте го она што ви е потребно во вашите функционални параметри, а Bevy автоматски го обезбедува! Ајде да ја напишеме нашата функција на играчот на движење. // 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 или Ресурси се парчиња на информации во текот на играта кои не се поврзани со ниту еден ентитет. Ви дава господар часовник на играта, така што секој систем го чита истиот "време од последната рамка" вредност. Res<Time> Explain Single<&mut Transform, With>? бара Bevy за точно еден ентитет кој има компонента, а исто така носи Таг. на part means we intend to modify that transform or player position (Remember, we added transform component in the setup function). If more than one player existed, this extractor would complain, which is perfect for a single-hero game. Single<&mut Transform, With<Player>> Transform Player &mut Transform What’s Vec2::ZERO? е дводимензионален вектор со двете вредности поставени на нула: Ние го користиме како почетна насока пред да го прочитаме влезот на тастатурата. Vec2::ZERO Vec2 { x: 0.0, y: 0.0 } What’s this KeyCode::… pattern? , … are enums (will cover enums later) that represent specific keys on the keyboard. Checking едноставно го праша Bevy дали тој клуч е задржан за време на тековната рамка. KeyCode::ArrowLeft KeyCode::ArrowRight input.pressed(KeyCode::ArrowLeft) Ние го игнорираме нулта насока, така што играчот стои неподвижен кога нема да се притиснат клучеви. конвертира векторот во должина 1, така што дијагоналното движење не е побрзо од право движење. вели колку пиксели во секунда да се движат, и враќа времето на рамката – бројот на секунди од претходниот рамка – така што множењето ги дава растојанието што треба да го поминеме ова ажурирање. normalize() speed time.delta_secs() Регистрирање на системот за движење // 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? се движи секоја рамка, па ние го приклучиме во Распоредот е вградена фаза на Bevy која се запалува еднаш по игра после стартувањето е завршено. 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? Системот е само функција на Rust што му ја давате на Bevy со леплива белешка која вели "изврши ме за време на стартувањето" или "изврши ме на секое ажурирање", а моторот должно го почитува. Ајде да го трчаме. Додавање на Sprite графика Ние ќе го користат универзалниот LPC SpriteSheet генератор за да им даде на нашиот карактер некои личност. Можете да ремиксирате делови од телото, облека и бои на и извезување на целосен шприц лист. Овој линк За овој проект, шприцот веќе е вклучен во Исфрлете ги датотеките за слики во така Bevy може да ги најде кога играта ќе работи. директориумот во src папката. Репо src/assets assets Рефакција на кодот Треба да додадеме повеќе код и додавањето на main.rs ќе го отежнува читањето и разбирањето. Ажурирајте го вашиот .dll датотека на следниот начин, исто така креирајте друг .dll датотека Пукаат во Разделба на код како ова го прави полесно да се зголеми проектот без една голема датотека прави сè. 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); } паѓа глобална поставка во апликацијата, така што секоја рамка започнува со бела позадина. .insert_resource(ClearColor(Color::WHITE)) е полнач на Bevy за текстури, аудио и други средства. so it looks inside Тоа е местото каде што живеат нашите шприци. AssetPlugin file_path src/assets Изградба на модулот на играчот Now that we have our basic app structure in place, it’s time to bring our character to life! We’ll create a dedicated Модул за справување со сè поврзано со нашиот мал авантурист. Ова го одржува нашиот код организиран и го прави полесно да додадете повеќе функции подоцна. player.rs Овој модул ќе ги задржи сите константи, компоненти и системи кои го прават нашиот играч да се движи, анимира и да комуницира со светот. Додадете го следниот код во 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 Овие константи им даваат имиња на броевите што ги користиме за spritesheet и движење математика: големина на плочки, рамки по ред, брзина на одење, и колку брзо анимацијата напредува. Маркерот ги задржува сите типови на играчи во еден модул. Player Дефинирање на насоките на играчите // 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 ги фаќа овие опции на едно место. Facing Why can’t I use a struct here? Една структура би имала потреба од еден куп булевари како , на , и ќе треба да ги чувате во синхронизација.Тоа го прави комплицирано.Enum гарантира дека ќе изберете само еден правец. facing_up:true facing_left:false When to decide between using enum and struct? Користете структура кога ви се потребни неколку полиња, како што е позиција со и , 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? Ајде да ги испишеме лицата за дневниците, и направи тривијално да се дуплира вредноста, и / овозможи проверка на еднаквост кога ги споредуваме насоките. Debug Clone Copy PartialEq Eq Why can’t I copy through simple assignment, why should I add these macros? По подразбирање, Rust ги поместува вредностите наместо да ги копира, така што компилаторот ве тера да се откажете. (и како помагач) вели "тоа е евтино, оди напред и дуплирај го". и Да ги споредиме двете страни директно, што е како ние откриваме кога играчот ја менува насоката. Copy Clone PartialEq Eq What do you mean by rust moves values, instead of copying? When you assign most values in Rust, the old variable stops owning it or in other words dies. Adding Двете варијабли се валидни. Copy Why does the old variable stop owning or die when assigned? Rust наметнува дека секоја вредност има еден сопственик, така што меморијата може безбедно да се ослободи. This is a bit going over my head! Да, не грижете се, имаме многу повеќе поглавја да одиме и како што се движите низ нив, ние ќе се справиме со ова. Компоненти на анимацискиот систем Кога имаме spritesheet со повеќе рамки (како нашата анимација за одење со 9 рамки), ни треба начин да контролираме колку брзо тие рамки играат. Размислете за тоа како за флип-книга - ако ги свртите страниците премногу бавно, анимацијата изгледа мрзливо. ни дава прецизна контрола над ова време, осигурувајќи дека анимацијата на нашиот лик изгледа глатко и природно на вистинската брзина. AnimationTimer Завртете го доказот so each player entity knows when to advance to the next animation frame. Each tick represents one slice of time; once the timer hits its interval we move to the next sprite in the sheet. 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? Нека нашата обвивка се преправа дека е внатрешноста Кога ќе го прочитаме, и го прави истото за писма.Тоа значи дека можеме само да се јавите е без рачно да ја извлечете внатрешната вредност прво. Deref Timer DerefMut timer.tick(time.delta()) AnimationTimer So we are renaming the Timer to AnimationTimer? Ние го обвиваме тајмерот, а не го преименуваме. како мала кутија која држи , плус етикета која вели "ова му припаѓа на анимацијата на играчот." и го вметнете во таа кутија, така што секој играч може да има свој тајмер ако ни требаат повеќе херои. AnimationTimer Timer Timer So it’s an instance of AnimationTime? Да да, е тупол структура која содржи а Ние градиме еден кога го создаваме плеер, така што секоја ентитет може да ги носи сопствените тајмерски податоци. Овој модел се појавува секогаш кога сакате да додадете дополнително значење на постоечки тип без да напишете сосема нов API. AnimationTimer Timer се сеќава на кој начин играчот укажува, дали тие се движат и дали тие само почнале или престанале.Системите го читаат ова за да изберат анимациски редови и да ги ресетираат рамките кога се менува движењето. AnimationState Поттикнување на играчот Ние го натоваруваме шприцот преку , создадете распоред на атлас на текстура, така што Беви ја знае мрежата, и изберете почетна рамка за херој кој се свртува надолу. 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)), )); } поставува почетна насока и знамиња дека ликот е празно во моментов и беше празно последниот рамка. AnimationState { facing, moving: false, was_moving: false } создава повторувачки часовник што го запалува секој секунди за да го унапредите шприцот. AnimationTimer(Timer::from_seconds(ANIM_DT, TimerMode::Repeating)) ANIM_DT ? What’s an AssetServer на е Bevy датотека за вметнување и менаџер кој се занимава со вметнување и кеширање на игра средства како што се слики, звуци и 3D модели. AssetServer Кога ќе се јавите , тоа не веднаш го вчита датотеката во меморијата. Наместо тоа, враќа рачка која можете да ја користите подоцна. Ова се нарекува "лезно вчитање" - вистинската вчитање на датотеката се случува во позадина, и Bevy ќе ве извести кога е подготвен. asset_server.load("path/to/sprite.png") Овој пристап е ефикасен затоа што: Повеќе ентитети можат да го споделат истиот сприт без да го вчитаат повеќе пати Активите се вчитаат само кога навистина се потребни Bevy може да ги оптимизира и да ги оптоварува активите за подобри перформанси Ако еден актив не се вчита, тоа нема да се урне вашата цела игра Во нашиот случај, го бара спитниот лист и враќа рачка за да го следи нејзиниот статус на полнење. asset_server.load("sprites/player.png") Систем за движење Сега што го имаме нашиот играч со сите потребни компоненти, треба да го ажурираме нашиот постоечки систем за движење за да ја следиме насоката со која се соочува играчот. Ова е од суштинско значење за анимацискиот систем да знае кој ред на spritesheet да го користи - секој правец (горе, надолу, лево, десно) одговара на различен ред во нашиот sprites Atlas. Овој ажуриран систем ќе ја открие насоката на движење и ќе го ажурира соодветно, така што нашиот анимациски систем може да го избере вистинскиот sprite ред за одење анимации во секоја насока. 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; } } Прашањето бара доказ за единствената ентитет означена ни дава мутабилен пристап до нејзината и Ние градиме вектор на насока од притиснатите клучеви, го нормализираме така што дијагоналниот влез не е побрз, и го движиме играчот со брзина × време на рамката.Логиката на свртување ја споредува хоризонталната против вертикалната сила за да одлучи на кој начин треба да изгледа спретот. Player Transform AnimationState имплементација на анимација Сега имаме сите парчиња на место - нашиот играч може да се движи во различни насоки, и ние се следи на кој начин тие се соочуваат. Овој систем ги зема информациите за насоката од нашиот систем за движење и го користи анимацискиот тајмер за да ги циклусира рамките со вистинската брзина.Тој се справува со комплексната логика на префрлување помеѓу различни анимациски редови кога играчот го менува насоката, и обезбедува непречено преминавање помеѓу ходање и неподвижни состојби. // 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, } } И двајцата го проверуваат резултатот и ги именуваат парчињата што ни се потребни. , на , and па можеме да ги користиме подоцна. Ако не успее (нема играч, или повеќе од еден), ќе го погодиме веднаш и излез. Rust го користи Тип за ова: Значи, прашањето врати точно еден резултат. значи „нешто во врска со тоа барање не се совпадна“. let Ok((mut anim, mut timer, mut sprite)) = query.single_mut() else { return; }; anim timer sprite else Result Ok Err Потоа ние е еден , што е типот на Rust "можеби има вредност". значи дека атласот на текстура постои и можеме да го прилагодиме; значи дека сè уште не е вчитано, па прескокнуваме и дозволуваме следната рамка да се обиде повторно. Тоа е истиот модел што ќе го користите кога ќе проверите мапа или кеш: користете ја вредноста само кога пребарувањето враќа нешто. match Option Some(atlas) None повлекува анимација состојба, тајмер, и sprite рачка за играчот. Тоа открива кој ред на атласот одговара на тековната насока, snaps на тој ред кога насоката се менува, и го користи тајмер за да се оди низ колони со стабилна брзина. Кога движењето престанува ние го ресетираме тајмер, така што анимацијата почива на последната рамка прикажана. animate_player Креирање на плеер плугин // 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)); } } Во Bevy, приклучокот е само структура која знае како да регистрира системи, ресурси и средства. за , ние му даваме на Bevy листа за проверка: секогаш кога овој приклучок е додаден во апликацијата, извршете го кодот внатре за да поставите сè што му е потребно на играчот. од да стане табла на играчи-специфични повици. PlayerPlugin Plugin PlayerPlugin main.rs на методот е контролната листа. Bevy ни дава мутабилност И ние се бориме за системите за кои се грижиме. Планирано е во така што sprite се појавува веднаш штом играта стартува. и Одете во планираат така што ќе ги извршуваат сите рамки – обработка на влез и анимација во lockstep. во автоматски го вклучува целиот проток на играчи. 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? Да. на карактеристиката вели: "Секој плугин мора да обезбеди Ние ја имплементираме таа карактеристика, па Руст очекува од нас да го снабдиме телото. Bevy го повикува овој метод кога го вчита приклучокот, што е причината зошто ги додаваме сите наши системи во него. Plugin build(&self, app: &mut App) What’s a trait? Карактеристика е договор кој ги опишува методите кои еден тип мора да ги обезбеди. Трет вели: „Дај ми а функција за да можам да ги регистрирам вашите системи.“ Со спроведување на оваа карактеристика за , ние се приклучивме на процесот на стартување на Bevy и вбризгуваме сопствен код за поставување. се однесува како и секој друг Bevy додаток, но инсталира нашите играчи-специфични системи. Plugin build PlayerPlugin PlayerPlugin Конечна интеграција // 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(); } Ајде да го трчаме. cargo run Оваа статија првично беше објавена на мојот блог. Оваа статија првично беше објавена на мојот блог. Повеќе поглавја за да дојдат, останете прилагодени.Поврзете се со мене на LinkedIn, следете ме на X за најновите ажурирања. Линколн X