Энэ нь миний Rust-Bevy сургалтын цуврал "The Impatient Programmer's Guide to Bevy and Rust: Build a Video Game from Scratch". Би энэ цуврал дахь Bevy болон тоглоомын хөгжүүлэх талаар дэлгэрэнгүй байх болно. Энэ цуврал дахь шинэ хувилбарууд дээр шинэчлэгдсэн байхын тулд бидэнтэй холбоотой байх. Энэ хуудсуугийн эх үүсвэр код нь энд байдаг. Холбоо барих Татаж авах At the end of this chapter, you’ll be able to achieve the results in the GIF below: Үйлчилгээ Баримтлал Баримтлал Хэрэв та суурилсангүй бол Үүнээс гадна, please follow the үүнийг бий болгох. Өнгөрсөн Албан ёсны гарын авлага Create a fresh project: cargo new bevy_game cd bevy_game Open and add Bevy: Cargo.toml [dependencies] bevy = "0.16.1" Та JavaScript, Python гэх мэт ямар ч нэг хэл дээр програмчлалын туршлагатай байх болно. Системүүд дээр дуудлага Бид татаж авах татаж авахыг боломжийг олгодог хялбар тоглолтыг бий болгохын тулд юу хэрэгтэй вэ? World System: Тоглоомын дэлхий үүсгэхийн тулд тоглогч дамжуулах боломжтой. Input System: Тавтай морилно уу тавтай морилно уу тавтай морилно уу тавтай морилно уу. Бид дээрх загварыг бий болгох тоглоом, үүсгэх / суурилуулах загварыг, тоглоомын статусын өөрчлөлтийг хянаж, тоглоомын дэлхий даяар суурь шинэчлэх загварыг харуулж байна. Тавтай морилно уу, Тавтай морилно уу! Ажуулалт: Зөвлөгөөс тоглогч руу дамжуулах, зөвлөгөөс бооцоо харахын тулд шалгах, үхсэн зөвлөгөөс элсэх, шинэ зөвлөгөөс хүрч. Bevy-ийн үндсэн дээр бид энэ хялбар Энэ суурилсан загвар нь Bevy-ийг хүчтэй, мэдэгдэх хялбар болгодог - Хэрэв та энэ концептыг мэдэгдэж байгаа бол, bevy-ийг бий болгох нь хялбар байх болно. setup and update system Setup гэж нэрлэдэг функцийг үзнэ үү, энэ нь танд аргумент болгон "командууд" өгөх бөгөөд энэ нь танд хүссэн зүйлийг үүсгэх, эсвэл үүсгэх боломжийг олгодог. // Pseudo code, doesn't compile fn setup(mut commands: Commands) { // Create anything you want commands.spawn() } What’s mut? Rust-д бид энэ үнэт өөрчилж төлөвлөж байна гэж явах хэрэгтэй. Үнэндээ, Rust-д хэлсэн үнэтүүд нь зөвхөн унших юм. Rust бид үнэт өөрчлөх төлөвлөж байна гэж хэлдэг. Rust энэ мэдлэг нь бүгд ангиллыг урьдчилан сэргийлэхийн тулд ашигладаг. mut mut Өнгөрсөн ? And what’s this mut commands: Commands Энэ нь таны тоглоомын дэлхийд зүйлсийг нэмэх боломжийг олгодог bevy библиотекид байна. Rust явжтай хэлбэрээр дуртай, Тиймээс компиляторууд энэ параметр Bevy-ийн орлого интерфэйс гэж мэддэг. Хэвлэх, Rust-ийг илрүүлэх боломжийг мэддэггүй бөгөөд энэ нь build-ийг хамардаг. :Commands What’s a type? Тип Rust-ийг хайж байна уу ямар ч төрлийн үнэ нь та боловсруулсан байна – тоо, текст, таймер, Bevy орлого, гэх мэт. Компилятор төрөл мэддэг дараа энэ нь танд гүйцэтгэсэн бүх үйл ажиллагаа нь бодит байгаа эсэхийг шалгаж болно. Isn’t this too much work? Энэ нь алдааг урьдчилан сэргийлэх, үйл ажиллагаа нь танд туслах, жишээ нь: Хэрэв та тоонд шууданг нэмэхийн тулд туршиж байгаа бол, компилятор тоглолтыг ажиллуулах өмнө танд зогсож болно. Үүнээс гадна, зөв төрөл мэддэг Rust-ийн өгөгдлийг хатуу багц боломжийг олгодог. А Энэ нь зөвхөн хоёр тоо юм, Тиймээс Rust зөв хоёр тоо хадгалах, ямар ч гайхамшигтай нэмэлт орон сууц, Хэрэв та хилүүд тоглоомын суурьтай байх үед зүйлсийг хурдан хадгалах. Эдгээр нь таны тоглоомын хэв маяг үр ашигтай байхын тулд туслах. Vec2 Хэрэв та JavaScript эсвэл Python-д Vec2 объектыг үүсгэхийн тулд, та зөвхөн хоёр тоо хадгалах биш юм. Runtime нь төрөл метадаат, шинж чанарын мэдээлэл, прототипийн хаяг нэмнэ - таны хялбар 8-байтын өгөгдлийн бүтэц ~48 байт хадгалах болно. Rust-ийн төрөл бүтэц нь компилятор цаг дээр ажилладаг. Та хоёр f32 газартай Vec2 бүтэц гэж нэрлэдэг үед энэ нь зөвхөн 8 байт, нэмэлт метадантаар хадгалагдаж байна. Компилятор нь типүүдтэй мэддэг бөгөөд энэ нь ажиллуулах хугацааны төрөл мэдээлэл шаардлагатай биш юм. 1000 тоглоомын суралцад энэ янз бүрийн драматик болж байна: Language Memory Usage Overhead Rust 8KB None Dynamic language ~48KB+ 6x overhead Өнгөрсөн 8КБ ямар ч Динамик хэл ~48KB + 6x ширхэг Энэ нь памэрийн хэрэглээний тухай биш юм - энэ нь гүйцэтгэлийн тухай юм. Хамгийн бага, урьдчилан сэргийлэх памэрийн байршуулалт нь CPU-ийн хашаа ашиглан илүү сайн юм. Энэ нь шууд фрэйм хурдтай тоглолтонд хуваалцаж, та бүр фрейм бүр илүүд нь суурьтай байдаг. Компилятор таны тоглолтонд ажиллуулах өмнө ширээний алдааг олж авах, хашаа сав баглаа боодол нь хурдан ажиллуулах болно. Камер дээр суурилсан What should we setup first? Бид камер хэрэгтэй, учир нь дэлгэц дээр ямар ч дэлгэц ямар ч. Дэлхийн өгөгдөл дээр байдаг байж болно, гэхдээ камер нь үнэхээр татаж авах зүйлсийг шийдвэрлэх болно. //Pseudo code, don't use this yet fn setup(mut commands: Commands) { commands.spawn(Camera2d) } What’s Camera2d? Bevy-ийн суурилсан 2D камераар багц юм. Энэ нь галзуу, та өөрийн 2D үзэсгэлэнт тохиромжтой үзнэ үү. Camera2d What’s a bundle? Багц нь зүгээр л танд ихэвчлэн нэгтгэсэн бүрэлдэхүүн хэсгүүдийн баг юм. Жишээ нь, Bevy нь Энэ багц нь байршил, текстура, өнгө нэхмэл, ирдэг; Энэ багц нь нэг дуудлагад sprite суралцаж өгдөг. SpriteBundle Бидний setup функцийг бүртгүүлэх Бид Startup дээр идэвхжүүлэхийн тулд bevy нь setup функцийг бүртгүүлэх хэрэгтэй. Ажуулалт Дараачийн код 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()…? creates the game application, load rendering, input, аудио, болон бусад систем, Бидний Startup функцийг бүртгүүлэх, болон Эдүүлбэр » Эдүүлбэр » Эдүүлбэр » Эдүүлбэр » Эдүүлбэр » Эдүүлбэр App::new() add_plugins(DefaultPlugins) add_systems(Startup, setup) run() Why register a startup function? What does it do? Startup систем нь анхны frame-ийн өмнө нэг удаа ажиллуулдаг. Бид камер, тоглогчид болон бусад зүйлсийг суулгахын тулд ашигладаг. What’s bevy main loop? Дараа нь, Bevy түүний гол хоолой руу хүрч байна: Энэ нь өгөгдлийн сонголтууд, таны системийг ажиллуулах, дэлхий даяар шинэчлэх, frame-ийг хуваалцах. Энэ хоолой та тоглолтонд дуусгахын тулд үргэлжлүүлэн байна. Бид үүнийг ажиллуулах cargo run Үнэгүй дэлгэц? Тавтай морилно уу, бид зүгээр л камераар суулгасан байна, Одоо бидний тоглогч нэмнэ. Тоглоомын тоглогч Одоо бидний тоглогч үүсгэх! Бидний мэдэгдэх Нөхцөл Үүнээс гадна, Bevy-д бүх зүйл нь Нөхцөл Эдгээр систем нь ажилладаг. Setup System Update System Entity Components Энэхүү суурь нь өвөрмөц ID (жишээ нь нийгмийн аюулгүй байдлын тоо) бөгөөд компонент нь энэ нь холбогдсон өгөгдөл гэж үзнэ үү. Манай тоглогч, бид аялал жуулчлалын хурд, эрүүл мэнд, эсвэл тусгай чадварыг шаарддаг. Rust-д эдгээр компонент үүсгэх хамгийн тохиромжтой арга юм. . struct Энэ нь зэвэрдэггүй гангийн үндсэн барилгын блок нэг юм. Энэ нь харьцуулахад харьцуулахад өгөгдлийг багтааж байна. Энд бид хязгаарлагдмал struct тавтай морилно уу тавтай морилно уу. Тавтай морилно уу. Тавтай морилно уу. struct Player // Place this before main function in main.rs #[derive(Component)] struct Player; Why tag? Tag нь дараагийн хайж авахын тулд суралцаж байна. зөвхөн манай эрэгтэйтэй холбогдсон, системиуд Bevy-ийг "Player-ийн тэгтэй суралцах" -ийг хайж болно, зөвхөн энэ нь хамтран ажиллах болно. Player What’s this #[derive(component)]? Rust-ийг энэ struct-д компонент макро кодыг холбохыг хэлдэг. Макро нь Rust-ийг таны хувьд урьдчилан тодорхойлолттай шаблон кодыг үүсгэх арга юм. automatically injects the boilerplate Bevy needs, so it can store and find Эмэгтэйчүүд, бидэнд нэг ижил хавхлага код нь бүхэнд хуваалцахын тулд хадгалж байна. Энэ цуврал дахь дараа нь макроны талаар дэлгэрэнгүй үзнэ үү. Энэ нь бидний тоглогч төрөл нь бүрэлдэхүүн хэсэг болгон болсон үед юм. derive #[derive(Component)] Player What’s a component, and why should the player be a component? Компонент нь суралцад холбогдсон өгөгдлийн бүрэлдэхүүн хэсэг юм. Ажлын, хурд, эрүүл мэнд, тэр ч байтугай "Энэ нь тоглогч юм" -ийн санаа нь эдгээр бүрэлдэхүүн хэсэгт амьдрал юм. нэг компонент бид дараа нь энэ суурь нь хайж болно, бусад компонент нэмж болно (жишээ нь, эрүүл мэнд, сав баглаа боодол), болон Bevy-ийн систем нь ихэвчлэн шинэчлэх шаардлагатай суурь сонгох болно. Player Одоогийн хувьд бид "@" символ ашиглан дэлгэц дээр бидний харагдах болно. Setup функцийг өөрчлөх. // 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? Tuple нь хавтгай дөрвөлжин хэлбэрийн үнэ цэнэтэй жагсаалттай жагсаалттай жагсаалттай жагсаалттай жагсаалттай жагсаалттай жагсаалттай жагсаалттай жагсаалттай жагсаалттай жагсаалттай жагсаалттай жагсаалттай. нь хоёр үнэ цэнэтэй харьцуулахад struct үүсгэхийн тулд шаардлагагүй байна. (Text2d::new("@"), TextColor(Color::WHITE)) Whats an entity? Энэхүү суурь нь нэгтгэсэн ID-ийг Bevy-ийг нэгтгэхийн тулд ашигладаг. Энэ нь ямар ч өгөгдлийг агуулдаг боловч та эдгээр бүрэлдэхүүн хэсгүүдийг холбогдсон үед энэ ID-ийг таны тоглоомын дэлхийд ямар нэг зүйлсийг харуулдаг. Бүх багц нь өвөрмөц ID, жагсаалттай бүрэлдэхүүн хэсгүүдтэй шинэ суурь үүсгэдэг бөгөөд энэ нь дараах хэлбэрээр үзнэ үү: Entity Components it carries #42 Camera2d #43 , , , , Text2d("@") TextFont TextColor Transform Player #42 Camera2d # 43 , , , Нөхцөл Text2d("@") TextFont TextColor Transform Player Once the queue flushes, those entities live in the world, ready for systems to discover them by the tags (components) they carry. We will be using this later when we want to do things like moving or animating them, or making them attack enemies, etc. Implementing Player Movement Now let’s create our for player movement! Think about what we need to move a player: Update System Keyboard Input - ямар ч ширээг дарна уу - to make movement smooth regardless of frame rate Time - to actually move the player Player position 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! Бидний Moving Player функцийг бичнэ үү. // 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 эсвэл Resources нь ямар ч нэг суурьтай биш юм тоглоомын бүх мэдээлэл хэсэг юм. Жишээ нь, Энэ нь танд тоглоомын мастерийн цаг арилжаа олгодог, Тиймээс бүх системийн "сүүлийн фреймээс хойш цаг" үнэ цэнэтэй байх болно. Res<Time> Explain Single<&mut Transform, With>? asks Bevy for exactly one entity that has a component and also carries the Тавтай морилно 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? is a two-dimensional vector with both values set to zero: . We use it as the starting direction before reading keyboard input. Vec2::ZERO Vec2 { x: 0.0, y: 0.0 } What’s this KeyCode::… pattern? , ... enums байна (тавтай морилно уу enums дараа нь) Тавтай морилно уу. зүгээр л Bevy-ийг энэ ключ нь одоогийн frame-ийн хооронд хадгалагдаж байгаа эсэхийг хүсдэг. KeyCode::ArrowLeft KeyCode::ArrowRight input.pressed(KeyCode::ArrowLeft) Бид нуль насос харах, тоглогч ямар ч ширээг даралтгүй байгаа үед тоглож байна. вектор нь урт 1 болгон хувиргадаг бөгөөд диагонал хөдөлгөөн нь шууд хөдөлгөөнээс хурдан биш юм. Энэ нь секундын хоорондоо илрүүлэх пикселийн тоо гэж хэлдэг бөгөөд Өнгөрсөн frame-ээс хойш секундын тоо - frame-time-г хуваалцахын тулд бид энэ шинэчлэлийг аялах ёстой хязгаарлалт олгодог. Өнгөрсөн нь бид тоглогчдийн трансформацийн хуваалцах нь энэ delta-ийг дэлгэц дээр sprite-ийг дамжуулахын тулд нэмнэ. 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? runs every frame, so we plug it into the schedule. 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? Exactly. A system is just a Rust function you hand to Bevy with a sticky note that says “run me during Startup” or “execute me on every Update,” and the engine dutifully obeys. Бид үүнийг ажиллуулах болно. Sprite график нэмэх Бид Universal LPC SpriteSheet Generator-ийг ашиглаж болно. Та биеийн эд анги, хувцас, өнгө өөрчилж болно. болон бүрэн spritesheet экспортын. Эдүүлбэр Энэ төсөл нь spritesheet нь одоо ч байтугай . Drop the provided image files into so Bevy can find them when the game runs. You will need to create the src хуудсууд дээр директорын. repo src/assets assets Refactoring Code We need to add more code and adding to main.rs will make hard to read and understand. Таны main.rs файлыг дараах хэлбэрээр шинэчлэх, мөн бусад file player.rs үүсгэхийн тулд pulls in the модуль, бидний гол файлыг хөндий байлгах. Энэ нь нэг том файлыг бүх зүйлийг хийхгүйгээр төслийг нэмэгдүүлэхэд хялбар болгодог. 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 Bevy’s loader for textures, audio, and other assets. We tweak its Энэ нь дотор , which is where our sprites live. AssetPlugin file_path src/assets Player модуль бий болгох Одоо бидний үндсэн апп бүтэц тохиромжтой байна, энэ нь бидний онцлог амьдрал авах үед байна! Модуль нь бидний жижиг авантурын талаар бүх зүйлийг удирдах. Энэ нь бидний код боловсруулсан, дараа нь илүү их шинж чанарыг нэмж илүү хялбар болгодог. 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 болон хөдөлгүүрийн математик хувьд ашиглаж байгаа тоо нь нэрлэдэг: ширээний хэмжээ, цуврал хоорондоо, хоорондоо хурд, анимацийн хурд. marker here keeps all player-specific types in one module. Player Defining Player Directions // 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 нь хязгаарлагдмал үнэ цэнэтэй жагсаалтаас жагсаалтаас жагсаалтаас жагсаалтаас жагсаалтаас жагсаалтаас жагсаалтаас жагсаалтаас харж байна. enum captures those options in one place. Facing Why can’t I use a struct here? A struct would need a bunch of booleans like , , and you’d have to keep them in sync. That makes it complicated. Enum guarantees you only pick one direction. facing_up:true facing_left:false When to decide between using enum and struct? Хэрэв та хэд хэдэн газрууд хэрэгтэй бол Struct ашиглах, жишээ нь and , эсвэл тоглогч статистик, эрүүл мэнд, хүч чадалтай. Та сонгосон сонголт сонгох үед enum ашиглах, жишээ нь тоглогч ямар нэг хэрэгсэл (зэвсэг, баглаа боодол, баглаа боодол) эсвэл ямар нэг менины дэлгэц идэвхжүүлдэг. x y Why the purpose of adding Debug, Clone, Copy, PartialEq, Eq macros? lets us print the facing for logs, and Энэ нь үнэ цэнэний дуплицийг trivial болгоно, болон / эвдэх шалгахын тулд бид насос харьцуулах. Debug Clone Copy PartialEq Eq Why can’t I copy through simple assignment, why should I add these macros? Үнэндээ Rust нь ихэвчлэн ихэвчлэн хуваалцахын тулд үнэ цэнэсийг хуваалцахын тулд компилятор танд opt-in болгодог. (Идэв As a helper) гэж хэлдэг "Энэ хямд юм, дараа нь үүнийг дублируулах болно." and let us compare two facings directly, which is how we detect when the player changes direction. 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 keeps both variables valid. Copy Why does the old variable stop owning or die when assigned? Rust нь бүр үнэ нь нэг эзэмшигчтай байхыг хүсч байна. Үзүүлэг нь аюулгүй байлгах болно. Бид дараагийн хувилбардаас гэрчилгээний нөхцөл, зайлуулах талаар дэлгэрэнгүй хэлэх болно. This is a bit going over my head! Тийм ээ, анхаарахгүй, бид илүү олон ангилалтай байдаг бөгөөд та тэднийг өнгөрүүлээрэй бол бид энэ нь шийдвэрлэх болно. Энэ нь одоо эдгээр концептуудыг мэдэрч биш юм. Анимацийн систем Components Бид олон frame-ийн spritesheet-тэй байх үед (9 frame-ийн хоосон анимацид шиг), бид энэ frame-ийн тоглох хурдны хяналтын арга хэрэгтэй. Таймс хяналтынгүйгээр бидний character нь нэг frame дээр хөвгөрсөн эсвэл frame-ийн хооронд дугуй, энэ нь blur шиг харуулж байна! Think of it like a flipbook - if you flip the pages too slowly, the animation looks choppy. If you flip too fast, you can’t see what’s happening. The Энэ нь бидэнд энэ цаг хугацааны тодорхой хяналтыг олгодог, бидний тэмдэгт хоосон анимацийг зөв хурдтай хялбар, байгалийн харж болно. AnimationTimer Үйлчлүүлэгчид Тэгэхээр бүр тоглогч суралцах үед дараагийн анимацийн frame-д урьдчилан сэргийлэх болно. Бүх tick нь цаг хугацааны нэг хуваалцтай; таймер өөрийн интервалтай дараа бид хуудас дахь дараагийн sprite-д орно. 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 when we read from it, and Энэ нь зүгээр л хэлбэрээр ашиглаж болно. Энэ нь Өнгөрсөн Эхлээд дотоодын түвшин гарын авлагагүйгээр. Deref Timer DerefMut timer.tick(time.delta()) AnimationTimer So we are renaming the Timer to AnimationTimer? Бид таймерг сав баглаа боодол байна, үүнийг өөрчилж биш юм. жижиг хайрцаг нь а , plus a label that says “this one belongs to the player animation.” When we spawn a player we create a fresh and tuck it into that box, so each player could have its own timer if we needed multiple heroes. AnimationTimer Timer Timer So it’s an instance of AnimationTime? Yes, is a tuple struct that contains a . Бид тоглогч үүсгэх үед нэг нь үүсгэх, тиймээс бүр суурь нь өөрийн таймер өгөгдлийг үүсгэх болно. Энэ загвар нь шинэ API бичлэггүйгээр одоогийн төрөлд нэмэлт ач холбогдолтой байхыг хүсэж байгаа үед харуулж байна. AnimationTimer Timer тоглогч ямар арга замыг мэдэгддэг, тэд хөдөлж байна уу, мөн тэд зүгээр л эхэлсэн эсвэл зогсоосон байна уу. Систем нь анимацийн цуврал сонгох, хөдөлгөөний өөрчлөлтийн үед фрэймс reset авахын тулд энэ унших. AnimationState Тоглоомын тоглогч Бид spritesheet дамжуулан , create a texture atlas layout so Bevy knows the grid, and pick the starting frame for a hero facing down. Then we spawn an entity with the sprite, transform at the origin, our marker components, and the timer that will drive animation. 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)), )); } эхлэх насос тохируулах, тэмдэглэгээ нь тэмдэг нь одоо хязгаарлагдмал бөгөөд хязгаарлагдмал өнгөрсөн frame байсан юм. AnimationState { facing, moving: false, was_moving: false } өөрчилсөн зууш үүсгэдэг бөгөөд бүх огноо Шилэн хуваалцахын тулд 2 секундын дотор. AnimationTimer(Timer::from_seconds(ANIM_DT, TimerMode::Repeating)) ANIM_DT ? What’s an AssetServer Нөхцөл Bevy-ийн файлын ачаалагч, менежер нь зураг, дуу, 3D загвар гэх мэт тоглоомын тоног төхөөрөмжүүдтэй ачаалал, кешүүлэхийн тулд. AssetServer Хэрэв та , it doesn’t immediately load the file into memory. Instead, it returns a handle that you can use later. This is called “lazy loading” - the actual file loading happens in the background, and Bevy will notify you when it’s ready. asset_server.load("path/to/sprite.png") Энэ арга нь үр дүнтэй, учир нь: Multiple entities can share the same sprite without loading it multiple times Активууд зөвхөн хэрэглэгддэг үед татаж болно Bevy can optimize and batch-load assets for better performance Хэрэв нэг ач холбогдол ачаалалгүй бол энэ нь таны бүх тоглолтонд ачаалалгүй болно In our case, spritesheet-ийг хайж, багасгалын статусыг харахын тулд гарын авлагаг дамжуулдаг. asset_server.load("sprites/player.png") Хүргэлтийн систем Now that we have our player spawned with all the necessary components, we need to update our existing movement system to track which direction the player is facing. This is crucial for the animation system to know which row of the spritesheet to use - each direction (up, down, left, right) corresponds to a different row in our sprite 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; } } Ангилал нь Tagged single entity-д Bevy-ийг хүсдэг бидэнд өөрчилж байлгахын тулд Нөхцөл . We build a direction vector from the pressed keys, normalize it so diagonal input isn’t faster, and move the player by speed × frame time. The facing logic compares horizontal vs vertical strength to decide which way the sprite should look. We record whether the player is moving now so later systems can detect when motion starts or stops. Player Transform AnimationState Анимацийн гүйцэтгэл Now we have all the pieces in place - our player can move in different directions, and we’re tracking which way they’re facing. The final piece is the animation system that actually updates the sprite frames to create the walking animation. Энэ систем нь бидний хөдөлгөөн системээс насос өгөгдлийг олж авах, анимацийн таймер ашиглан sprite frame-ийн хооронд зөв хурдтай хоолой хийх. Энэ систем нь тоглогч нь насос өөрчлөгдсөн үед өөр өөр анимацийн цуврал хоорондын хоорондын хоорондын хоорондоо транзицийг хангах. // 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, } } Бүх нь үр дүнд шалгаж, бидний шаардлагатай эд зүйлсийг нэрлэдэг. Хэрэв асуултууд амжилттай байгаа бол, код Нөхцөл Үнэгүй Бид дараа нь тэднийг ашиглаж болно. Хэрэв энэ нь хэзээ ч (нээр ямар ч тоглогч, эсвэл нэгээс дээш), бид галзуу, шууд галзуу. Rust Энэ нь төрөл: Энэ нь “query returned exactly one result” гэж нэрлэдэг. гэсэн үг юм "Энэ асуултанд ямар нэг зүйл харьцуулагдсангүй." let Ok((mut anim, mut timer, mut sprite)) = query.single_mut() else { return; }; anim timer sprite else Result Ok Err Дараа нь бид Энэ нь , Rust-ийн "Maybe there is a value" төрөл юм. means the texture atlas exists and we can tweak it; Энэ нь дэлгэц эсвэл хашаа шалгах үед ашиглаж буй нэг загвар юм: зүгээр л хайж байгаа үед үнэт ашиглах. match Option Some(atlas) None Энэ нь тоглогчид зориулсан анимацийн нөхцөл, таймер, sprite гарын авлагатай. Энэ нь атлас нь одоогийн гарын авлагатай цуглуулж, гарын авлага өөрчлөгдсөн үед энэ цуглуулж, таймер ашиглан хоорондын хоорондын хоорондын хооронд тогтвортой хурдаар хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоорондын хоолой. animate_player 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)); } } Бидний "Player модуль" нь Plug-in хэлбэрээр юм. Bevy-д, Plug-in нь систем, ресурс, тоног төхөөрөмж бүртгүүлэхийн тулд мэддэг struct юм. for , бид Bevy-ийг шалгах жагсаалт өгдөг: Энэ хувилбар нь апп-д нэмж байгаа үед бүх тоглогч шинж чанарыг тохируулахын тулд дотор код ажиллуулах. тоглогч-специфичтой зовооны хавтан болж байна. PlayerPlugin Plugin PlayerPlugin main.rs Нөхцөл Метод нь шалгах жагсаалт юм. Bevy бидэнд мутабель дамжуулдаг , and we bolt on the systems we care about. Энэ нь төлөвлөж байна гэхдээ sprite нь тоглолтонд эхлэх үед харуулж болно. Нөхцөл Өнгөрсөн програм хангамж нь тэдний бүх frame-г гүйцэтгэхийн тулд - lockstep-д input болон анимацийг боловсруулах. Энд хэлсэн бүх зүйл нь, Өнгөрсөн автоматаар бүх тоглогч дамжуулагч дамжуулах. 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? Үнэгүй Түлхүүр үг: "Хэв бүрийн хувилбар нь функц.” Бид энэ шинж чанарыг гүйцэтгэхийн тулд Rust нь биеийн хангахын тулд биднийг хүлээж байна. Bevy нь энэ арга зам дээр тавтай морилно уу, учир нь бид түүний дотор манай бүх системийг нэмнэ. Plugin build(&self, app: &mut App) What’s a trait? Түлхүүр үг нь төрөл бүрийн хэрхэн хангах ёстой. Bevy's Түлхүүр үг: "Give me a системд бүртгүүлэхийн тулд үйл ажиллагаа явуулж болно. " , бид Bevy-ийн эхлүүлэх үйл явцд холбогдсон бөгөөд бидэнтэй суурилуулах кодыг эвдэж байна. Характер нь өөр төрөл бүрийн ач холбогдолтой байх боломжийг олгодог, Бүх Bevy Plugin-ийн шиг ажилладаг боловч энэ нь манай тоглогч-ийг тусгай системийг суулгах болно. Plugin build PlayerPlugin PlayerPlugin Өнгөрсөн Integration // 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(); } Let’s run it. cargo run Энэ нийтлэл эхлээд миний блог дээр Posted. Энэ нийтлэл эхлээд миний блог дээр Posted. More chapters to come, stay tuned. Connect with me on , follow me on for latest updates. LinkedIn X Бүртгүүлэх X