Hii ni sura ya 1 ya mfululizo wangu wa mwongozo wa Rust-Bevy "Mwandishi wa Mwandishi wa Impatient kwa Bevy na Rust: Kujenga Mchezo wa Video kutoka Nyuma". Nitaingia kwa kina katika Bevy na maendeleo ya mchezo katika mfululizo huu, na pia utafunika NPCs zinazotumiwa na Watendaji wa AI. Kujiunga na jumuiya yetu ili kuwa na update juu ya matoleo mapya katika mfululizo huu. Kujiunga na Jumuiya yetu Inapatikana hapa At the end of this chapter, you’ll be able to achieve the results in the GIF below: Maelekezo ya Setup Muundo wa Ikiwa huwezi kuunda Hata hivyo, tafadhali kufuata ya kuweka juu. ukimya Mwongozo wa rasmi Create a fresh project: cargo new bevy_game cd bevy_game Open and add Bevy: Cargo.toml [dependencies] bevy = "0.16.1" Natumaini una uzoefu wa programu katika lugha yoyote kama JavaScript au Python. Kufikiri kwa mifumo Ni nini tunahitaji kujenga mchezo rahisi ambayo inaruhusu mchezaji kuhamia kutoka kwenye ufungaji wa keyboard? Mfumo wa Dunia: Kujenga ulimwengu wa mchezo ambapo mchezaji anaweza kuhamia. Mfumo wa Input: Ufuatiliaji wa kuingia kwa keyboard na kutafsiri kwa harakati ya mchezaji. Tunaona mfano wa hapo juu wa kawaida sana katika kujenga michezo, mfano wa kuunda / kuanzisha na mfano wa kufuatilia mabadiliko ya hali ya mchezo na kuboresha vitu vya ulimwengu wa mchezo. Setup: Kuunganisha adui na tabia tofauti, kupakia sprites ya adui na animations, kujenga afya ya adui na viwango vya uharibifu. Update: Kuendesha adui kwa mchezaji, angalia kama adui ni kushambuliwa na risasi, kuondoa adui wafu na kuzalisha adui mpya kwa muda. Kwenye msingi wa Bevy, tuna hii rahisi Mfano huu wa msingi ni nini hufanya Bevy nguvu na rahisi kuelewa - mara tu kuelewa dhana hii, kujenga na bevy inakuwa rahisi. setup and update system Fikiria kazi inayoitwa setup, ambayo inakupa "amri" kama hoja, na inakuwezesha kuzalisha au kuzalisha chochote unachotaka. // Pseudo code, doesn't compile fn setup(mut commands: Commands) { // Create anything you want commands.spawn() } What’s mut? katika Rust tunahitaji kutaja wazi kwamba tunapanga kubadilisha thamani hii. Kwa default, Rust hutibiwa thamani zilizotajwa kama kusoma tu. inasema Rust tuna mpango wa kubadilisha thamani. Rust inatumia ujuzi huu kuzuia darasa nzima la bugs. mut mut ya ? And what’s this mut commands: Commands Ni kutoka kwenye maktaba ya bevy, ambayo inaruhusu kuongeza vitu kwenye ulimwengu wako wa mchezo. Rust anapenda aina za wazi, hivyo inawezesha mhariri kujua kiwango hiki ni interface ya amri ya Bevy. Kupiga mbinu na Rust hauwezi kuwa na uhakika wa kile kinachoonyeshwa, hivyo huzuia kujenga. :Commands What’s a type? A aina inatuambia Rust ni aina gani ya thamani ambayo unakabiliwa - idadi, maandishi, timers, amri za Bevy, na kadhalika. Mara baada ya mhariri anajua aina hiyo, anaweza kuangalia kwamba kila operesheni unayofanya juu yake kwa kweli ina maana. Isn’t this too much work? Ni kuzuia makosa na kukusaidia na utendaji, kwa mfano: Ikiwa unajaribu kuongeza mstari kwa namba, compiler inakuzuia kabla ya mchezo uendeshaji. ya ni namba mbili tu, hivyo Rust hutoa namba mbili sawa, hakuna mshangao nafasi ya ziada, ambayo inahifadhi mambo haraka wakati una maelfu ya vitu vya mchezo. Vec2 Wakati wa kuunda Object Vec2 katika JavaScript au Python, huna tu kuhifadhi namba mbili. wakati wa uendeshaji huongeza data ya aina, maelezo ya mali, na viungo vya prototype - kurekebisha muundo wa data rahisi wa 8-byte katika ~48 bytes ya kumbukumbu. Mfumo wa aina ya Rust hufanya kazi wakati wa kutafsiri. Unapozungumzia muundo wa Vec2 na mashamba mawili ya f32, hiyo ndio inapatikana - tu bytes 8, hakuna metadata ya ziada. Muundo tayari unajua aina, hivyo hakuna taarifa ya aina ya runtime inahitajika. Pamoja na vitu vya 1000 vya mchezo, tofauti hii inakuwa ya kushangaza: Language Memory Usage Overhead Rust 8KB None Dynamic language ~48KB+ 6x overhead ukimya 8Kb ya Hakuna Lugha ya Dynamic kwa kiwango cha 48kb 6x juu ya kichwa Hii sio tu kuhusu matumizi ya kumbukumbu - ni juu ya utendaji. mipangilio ya kumbukumbu ndogo, inamaanisha matumizi bora ya CPU cache, ambayo moja kwa moja inamaanisha kiwango cha frame ya haraka katika michezo ambapo wewe ni usindikaji maelfu ya vitu kila frame. Kuweka kamera ya What should we setup first? Tunahitaji kamera kwa sababu hakuna kitu kinachoonyeshwa kwenye skrini bila moja. dunia inaweza kuwepo katika data, lakini kamera inachagua kile kinachotolewa. //Pseudo code, don't use this yet fn setup(mut commands: Commands) { commands.spawn(Camera2d) } What’s Camera2d? ni mfuko wa kamera ya Bevy iliyopangwa ya 2D. Piga na unapata mtazamo wa tayari kwa kwenda wa eneo lako la 2D. Camera2d What’s a bundle? Picha hii ilipigwa tokea ktk veranda za moja ya vyumba vya Manyara Serena Lodge. ambayo inachangia nafasi, texture, rangi ya rangi, na kuonekana; kuzaa bundle hiyo inatoa kiini cha sprite katika wito mmoja. SpriteBundle Kujiandikisha kwa kazi yetu ya kuanzisha Tunahitaji kusajili kazi yetu ya kuanzisha na bevy kutumika kwenye Startup. Ujumbe wako kwa mujibu wa msimbo unaofuata. 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? ni kama kit ya mwanzo, vitu unavyopata mara kwa mara wakati wa kujenga mchezo - ya , viungo, msaidizi wa kimantiki, nk bevy::prelude App Commands What’s App::new()…? kuunda maombi ya mchezo, uendeshaji mzigo, kuingia, sauti, na mifumo mingine, kurekodi kazi yetu ya kuanza, na Kuanza kucheza katika TopSlotSite , kufuata 3 hatua rahisi kujiandikisha. App::new() add_plugins(DefaultPlugins) add_systems(Startup, setup) run() Why register a startup function? What does it do? Mfumo wa uanzishaji unaendesha mara moja kabla ya ramani ya kwanza. Tunatumia kuweka kamera, wachezaji, na mambo mengine ambayo yanapaswa kuwepo mara tu window inapofungua. What’s bevy main loop? Baada ya uanzishaji, Bevy inaingia katika mzunguko wake kuu: inapiga kura ya kuingia, huendesha mifumo yako, inakaribisha ulimwengu, na hufanya frame. Hebu tuendane na hilo cargo run A blank screen? Yup, we have only setup the camera, now let’s add our player. Kuweka mchezaji Sasa sisi kujenga wachezaji wetu! kukumbuka yetu na ya Kwa mfano, katika uwanja huu, kila kitu ni kwa Mfumo huu unaweza kufanya kazi. Setup System Update System Entity Components Fikiria kiini kama ID ya kipekee (kama nambari ya usalama wa kijamii) na vipengele kama data inayohusishwa nayo. Kwa mchezaji wetu, tunahitaji vipengele kama kasi ya harakati, afya, au uwezo maalum. . struct ni moja ya viungo vya msingi vya ujenzi wa chuma. Inachanganya data sawa. Hapa tunatangaza struct hivyo aina yenyewe inafanya kazi kama alama tunaweza kuunganisha na mwili wa mchezaji. baadaye tunaweza kuongeza mambo kama afya ya mchezaji. struct Player // Place this before main function in main.rs #[derive(Component)] struct Player; Why tag? Kuanzisha mlinzi juu ya msalaba wa baadaye. kwa sababu inashikiliwa tu kwa shujaa wetu, mifumo inaweza kuuliza Bevy, "kutoa mwili na alama ya mchezaji" na kazi na moja tu. Player What’s this #[derive(component)]? Inakuambia Rust kuunganisha msimbo wa makro ya sehemu kwa struktur hii. Makro ni njia ya Rust ya kuzalisha msimbo wa template uliopangwa kwako. moja kwa moja inachukua boilerplate Bevy inahitaji, hivyo inaweza kuhifadhi na kupata makundi, sisi kuokoa kutoka nakala sawa coding kila mahali. Tutachunguza makundi kwa karibu baadaye katika mfululizo. Hii ni wakati wa mchezaji yetu aina inakuwa sehemu. derive #[derive(Component)] Player What’s a component, and why should the player be a component? Sehemu ni kipande cha data kilichohusishwa na kiini. nafasi, kasi, afya, na hata wazo la "hili ni mchezaji" wote wanaishi katika vipengele. sehemu ambayo tunaweza kutafuta kwa entity hiyo baadaye, kuongeza vipengele vingine (kama vile afya au hifadhi), na kuruhusu mifumo ya Bevy kuchagua hasa entities ambazo zinahitaji kuboresha. Player Kwa sasa tutawakilisha tabia yetu kwenye skrini kwa kutumia ishara ya "@". // 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, )); } inachukua tuple hiyo na kutibu kama bundle ya vipengele. wito huu mmoja unajumuisha maandishi tunayotaka kuonyesha, mipangilio yake ya maandishi, rangi, nafasi, na Timu ambayo inajulikana kama Entity. commands.spawn(( ... )) Player What’s a tuple? Tuple ni orodha ya maagizo yaliyoandikwa kwa safu. Rust inachunguza kila nafasi, hivyo inachukua thamani mbili peke yake bila haja ya kuunda muundo. (Text2d::new("@"), TextColor(Color::WHITE)) Whats an entity? Entity ni ID ya kipekee ambayo Bevy hutumia kuunganisha vipengele pamoja. Kwa mwenyewe haina data, lakini mara tu unayounganisha vipengele, ID hiyo inawakilisha kitu katika ulimwengu wako wa mchezo. Kila bundle hutoa kiini kipya na ID ya kipekee na vipengele vilivyoorodheshwa, ambayo unaweza kuonyesha kama hii: Entity Components it carries #42 Camera2d #43 , , , , Text2d("@") TextFont TextColor Transform Player #42 ya Camera2d # 43 ya ya ya ya ya Text2d("@") TextFont TextColor Transform Player Mara baada ya mstari wa kusafisha, viumbe hawa wanaishi ulimwenguni, tayari kwa mifumo ya kugundua kwa alama (komponents) ambazo hutoa.Tutaitumia hii baadaye wakati tunataka kufanya mambo kama kuhamisha au kuhimiza, au kuwafanya kushambulia adui, nk. Utekelezaji wa harakati za wachezaji Hebu tufanye kazi yetu ya kwa harakati ya mchezaji! kufikiri juu ya nini tunahitaji kuhamisha mchezaji: Update System Upatikanaji wa Keyboard - kujua kifungo kinachowekwa Muda - kufanya harakati rahisi bila kujali kiwango cha frame nafasi ya mchezaji - kwa kweli kuhamisha mchezaji Katika injini nyingine za michezo, ungependa kutumia muda wa kuunganisha mifumo hii pamoja. Lakini hapa ni uchawi wa Bevy - unahitaji tu kile unachohitaji katika vigezo vyako vya kazi, na Bevy inatoa moja kwa moja! Hebu tuandikishe kazi yetu ya mchezaji wa kuhamia. // 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 au rasilimali ni sehemu ya habari ya michezo ambayo haihusiani na mtu yeyote. inakuja na saa kuu ya mchezo, hivyo kila mfumo anasoma thamani sawa ya "mara tangu frame iliyopita". Res<Time> Explain Single<&mut Transform, With>? asks Bevy for exactly one entity that has a sehemu na pia hufanya kazi kwa ya ya ya ya ya sehemu inamaanisha tuna nia ya kurekebisha kwamba mabadiliko au mchezaji nafasi (Kumbuka, sisi aliongeza mabadiliko sehemu katika kazi ya kuanzisha). kama zaidi ya mchezaji mmoja kulikuwa, extractor hii ingekuwa kulalamika, ambayo ni bora kwa ajili ya mchezo single-hero. Single<&mut Transform, With<Player>> Transform Player &mut Transform What’s Vec2::ZERO? ni vector mbili-dimensional na thamani zote mbili zimewekwa kwa zero: Tunatumia kama mwelekeo wa kuanza kabla ya kusoma ufungaji wa keyboard. Vec2::ZERO Vec2 { x: 0.0, y: 0.0 } What’s this KeyCode::… pattern? ya ... ni enums (watakuwa cover enums baadaye) ambazo zinawakilisha kifungo maalum kwenye keyboard. Unaweza tu kuuliza kama kifungo hicho kinahifadhiwa wakati wa ramani ya sasa. KeyCode::ArrowLeft KeyCode::ArrowRight input.pressed(KeyCode::ArrowLeft) Sisi kupuuza null mwelekeo hivyo mchezaji ni imara wakati hakuna nywila ni kuchomwa. inabadilisha vektor kwa urefu wa 1, hivyo harakati ya diagonal sio haraka kuliko harakati ya moja kwa moja. inaonyesha ni kiasi gani cha pixels kwa sekunde kuhamia, na inatoa muda wa frame - idadi ya sekunde tangu frame iliyopita - hivyo kuongezeka kwao hutoa umbali tunapaswa kusafiri kwa update hii. Hatimaye, tunaongeza delta hiyo kwenye tafsiri ya mabadiliko ya mchezaji ili kuhamisha sprite kwenye skrini. normalize() speed time.delta_secs() Kujiandikisha kwa mfumo wa harakati // 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? Kila kitu kimefungwa, kwa hivyo tunapaswa kuingiza. Mpango wa. ni hatua ya Bevy iliyoundwa ambayo hufungua mara moja kwa mzunguko wa mchezo baada ya uanzishaji uliofanyika. 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? Mfumo ni tu kazi ya Rust ambayo unatoa kwa Bevy na kumbukumbu iliyoandikwa ambayo inasema "kuendesha wakati wa Startup" au "kuendesha mimi juu ya kila Updates," na injini hutii kwa makini. Hebu tuendane na hilo. Adding Sprite Graphics Tutatumia Universal LPC SpriteSheet Generator kutoa tabia yetu. Unaweza remix sehemu za mwili, mavazi, na rangi katika na kusambaza karatasi kamili ya kuhifadhi. Uhusiano huu Kwa mradi huu spritesheet tayari ni pamoja na katika Piga faili ya picha iliyotolewa kwenye hivyo Bevy inaweza kupata yao wakati mchezo unaendesha. utahitaji kuunda Maelezo ya ndani ya folder ya src. ya repo src/assets assets Msimbo wa Refactoring Tunahitaji kuongeza msimbo zaidi na kuongeza kwa main.rs itakuwa vigumu kusoma na kuelewa. Tafadhali kuboresha faili yako ya .dll kwa njia zifuatazo, pia uunda faili nyingine ya .dll Kuingia kwenye Moduli, kuweka faili yetu kuu ndogo. Kupiga msimbo kama hii inafanya iwe rahisi kukua mradi bila faili moja kubwa kufanya kila kitu. 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); } kupoteza mipangilio ya kimataifa kwenye programu ili kila frame kuanza na background nyeupe. .insert_resource(ClearColor(Color::WHITE)) ni mwendeshaji wa Bevy kwa textures, sauti, na mali nyingine. Sisi tweak yake so it looks inside Hiyo ni mahali ambapo watu wetu wanaishi. AssetPlugin file_path src/assets Kuunda Moduli ya Mchezaji Sasa tunayo muundo wa msingi wa programu yetu, ni wakati wa kuleta tabia yetu kwa maisha! module kushughulikia kila kitu kinachohusiana na adventurer yetu ndogo. Hii inahifadhiwa code yetu na inafanya iwe rahisi kuongeza vipengele zaidi baadaye. player.rs Moduli hii itahifadhi viwango vyote, vipengele, na mifumo ambayo inafanya mchezaji wetu kuhamia, kuishi, na kuingiliana na ulimwengu. Ongeza code zifuatazo katika 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 Kiwango hiki kinatoa majina kwa idadi tunayoitumia tena kwa spritesheet na matibabu ya harakati: ukubwa wa tile, ramani kwa mstari, kasi ya kutembea, na jinsi haraka animation inavyoendelea. marker hapa inahifadhi aina zote za mchezaji maalum katika moduli moja. Player Kuweka miongozo ya wachezaji // 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? Jina la utani El Nano, a pseudonym mfano kwa Fernando katika Asturias, nafasi yake ya kuzaliwa, Alonso vitendo kama Balozi wa ukarimu kwa UNITA. Enum inashughulikia chaguzi hizi katika sehemu moja. Facing Why can’t I use a struct here? Mtu anahitaji mimea ya boolean kama ya , na unahitaji kuweka yao katika sync. ambayo inafanya kuwa ngumu. Enum inahakikisha unachagua mwelekeo mmoja tu. facing_up:true facing_left:false When to decide between using enum and struct? Tumia struk wakati unahitaji mashamba kadhaa, kama vile nafasi na na ya , au takwimu za mchezaji na afya na uvumilivu. Tumia enum wakati unachagua chaguo moja kutoka kwenye orodha, kama vile chombo ambacho mchezaji anachukua (saba, shaba, shaba) au ambayo screen ya menus ni kazi. x y Why the purpose of adding Debug, Clone, Copy, PartialEq, Eq macros? Hebu tuangalie kwa makini mapendekezo yaliyotolewa. na ya kufanya hivyo trivial kwa duplicate thamani, na / kuwezesha udhibiti wa usawa wakati sisi kulinganisha mwelekeo. Debug Clone Copy PartialEq Eq Why can’t I copy through simple assignment, why should I add these macros? Kwa default, Rust huhamisha thamani badala ya kuiga, hivyo compiler inakuwezesha kuchagua. (Na kwa kama msaidizi) anasema "ni bei nafuu, kwenda mbele na duplicate." na ya Hebu tufanye kulinganisha pande mbili moja kwa moja, ambayo ni jinsi tunavyotambua wakati mchezaji anabadilika mwelekeo. Copy Clone PartialEq Eq What do you mean by rust moves values, instead of copying? Unapowekwa thamani nyingi katika Rust, mabadiliko ya zamani huacha kumiliki au, kwa maneno mengine, hufa. Pamoja na hayo, mabadiliko yote mawili yanaweza kuwa sahihi. Copy Why does the old variable stop owning or die when assigned? Rust inahakikisha kwamba kila thamani ina mmiliki mmoja ili kumbukumbu inaweza kuachiliwa kwa usalama. This is a bit going over my head! Ndiyo, usijali, tuna vipengele vingi zaidi vya kwenda na kama unavyoendelea kupitia, tutakabiliana na hili. Sehemu ya mfumo wa animation Wakati sisi kuwa na spritesheet na ramani nyingi (kama vile yetu 9 ramani walking animation), tunahitaji njia ya kudhibiti jinsi ya haraka kwamba ramani kucheza. Fikiria kama flipbook - kama wewe kurudi kurasa polepole sana, animation inaonekana choppy. kama wewe kurudi haraka sana, huwezi kuona nini kinaendelea. inatuwezesha kudhibiti kwa usahihi wakati huu, kuhakikisha animation ya kutembea ya tabia yetu inaonekana ladha na asili kwa kasi sahihi tu. AnimationTimer Kuanzisha mlinzi juu ya hivyo kila mchezaji anajua wakati wa kuhamia kwenye ramani ya animation inayofuata. Kila tick inawakilisha kipande kimoja cha muda; mara tu timer inapiga kiwango chake sisi kuhamia sprite ijayo katika karatasi. 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? kuacha wrapper yetu kuonekana kuwa ya ndani Baada ya kusoma hayo, na kufanya hivyo kwa ajili ya maandishi. ambayo inamaanisha tunaweza tu kuita ya bila kuondoa manually thamani ya ndani kwanza. Deref Timer DerefMut timer.tick(time.delta()) AnimationTimer So we are renaming the Timer to AnimationTimer? Sisi ni kufunika timer, si jina lake. kufikiri kama chombo kidogo ambacho kinahifadhi , pamoja na alama ambayo inasema " hii ni ya animation ya mchezaji."Kama sisi kuzaliwa mchezaji sisi kujenga mpya na kuweka ndani ya sanduku hilo, ili kila mchezaji angeweza kuwa na timer yake mwenyewe ikiwa tunahitaji mashujaa kadhaa. AnimationTimer Timer Timer So it’s an instance of AnimationTime? Kwa kweli, Hiyo ni mstari wa juu ambayo ina a Sisi kujenga moja wakati sisi kuzalisha mchezaji ili kila kitu kinaweza kuleta data yake mwenyewe timer. mifano hii inaonyeshwa wakati wowote unataka kuongeza maana kwa aina iliyopo bila kuandika API mpya kabisa. AnimationTimer Timer kukumbuka ni njia gani mchezaji anatoa pointi, kama wao ni kuhamia, na kama wao tu kuanza au kusimamishwa. mifumo kusoma hii kuchagua mstari animation na kurekebisha ramani wakati harakati mabadiliko. AnimationState Kuondoa mchezaji Tunaweza kupakua spritesheet kupitia , kuunda muundo wa atlas texture ili Bevy anajua grid, na kuchagua ramani ya kuanza kwa shujaa anayeangalia chini.Kisha tunazaliwa entity na sprite, kubadilisha katika asili, vipengele vyetu vya alama, na timer ambayo itaendesha 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)), )); } inatoa mwelekeo wa kuanza na bendera kwamba tabia ni tupu sasa na ilikuwa tupu frame iliyopita. AnimationState { facing, moving: false, was_moving: false } kuunda kifungo cha mara kwa mara ambacho kinasababisha moto kila Sekunde chache kwa ajili ya kuharakisha meza. AnimationTimer(Timer::from_seconds(ANIM_DT, TimerMode::Repeating)) ANIM_DT ? What’s an AssetServer ya ni uendeshaji wa faili na msimamizi wa Bevy ambaye anashughulikia kupakia na kuhifadhi rasilimali za mchezo kama picha, sauti, na mifano ya 3D. AssetServer Wakati wa kuita , haina mara moja kupakia faili katika kumbukumbu. Badala yake, inapiga mkono ambao unaweza kutumia baadaye. Hii inajulikana kama "kuchukua lazy" - kupakia faili halisi hutokea katika background, na Bevy itakuambia wakati ni tayari. asset_server.load("path/to/sprite.png") Njia hii ni ya ufanisi kwa sababu: Mashirika mengi yanaweza kushiriki sprite moja bila ya kupakia mara kadhaa Fedha zinapatikana tu wakati zinahitajika Bevy inaweza kuboresha na usambazaji wa mali kwa utendaji bora Ikiwa rasilimali moja haifai kupakia, haitaanguka mchezo wako mzima Katika kesi yetu, kuomba karatasi ya sprites na kurejesha mkono wa kufuatilia hali yake ya kupakia. asset_server.load("sprites/player.png") Mfumo wa harakati 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. Mfumo huu unaofanywa utaona mwelekeo wa harakati na urekebisha kulingana, hivyo mfumo wetu wa animation anaweza kuchagua mstari sahihi wa sprite kwa animations ya kutembea katika kila mwelekeo. 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; } } Utafiti unahitaji kuthibitisha kwa kitu kimoja kilichotajwa ili kuwezesha upatikanaji wa muundo wa na ya Tunaunda vektor ya mwelekeo kutoka kwa vifungo vilivyopigwa, kuimarisha ili kuingia kwa njia ya diagonal si haraka zaidi, na kuhamisha mchezaji kwa kasi × muda wa frame. Logic ya kukabiliana inashughulikia nguvu ya horizontal vs nguvu ya mstari ili kuamua jinsi sprite inapaswa kuangalia. Player Transform AnimationState Utekelezaji wa Animation Sasa tuna vipande vyote mahali - mchezaji wetu anaweza kuhamia katika mwelekeo tofauti, na sisi ni kufuatilia ni njia gani wao ni mbele. kipande cha mwisho ni mfumo wa animation ambayo kwa kweli update sprites kuunda animation kutembea. Mfumo huu unachukua habari ya mwelekeo kutoka kwa mfumo wetu wa harakati na hutumia timer ya animation kuendesha kupitia viwambo vya sprite kwa kasi sahihi. Inashughulikia mantiki ngumu ya kubadilisha kati ya mstari tofauti wa animation wakati mchezaji anabadilika mwelekeo, na inahakikisha mabadiliko salama kati ya hali ya kutembea na starehe. // 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, } } wote kuangalia matokeo na majina ya vipande tunahitaji. Ikiwa query inafanikiwa, msimbo huunganisha ya na ya Hivyo tunaweza kutumia baadaye. Ikiwa haina kushindwa (hakuna mchezaji, au zaidi ya mmoja), tunashambulia Kuanzisha mlinzi juu ya msalaba wa msalaba. at Jinsi ya kufanya hii: Inamaanisha "Utafiti ulirudisha matokeo ya moja kwa moja," Hiyo ina maana "kila kitu kuhusu query hiyo haikufanana." let Ok((mut anim, mut timer, mut sprite)) = query.single_mut() else { return; }; anim timer sprite else Result Ok Err Baada ya hapo sisi Mmoja wa , ambayo ni aina ya "maybe there is a value" ya Rust. ina maana atlas texture kuna na tunaweza kurekebisha; Hii inamaanisha kwamba bado haijafungwa, kwa hiyo tunashindwa na kuruhusu ramani inayofuata kujaribu tena. Ni mfano huo unaotumika wakati wa kuangalia ramani au cache: kutumia thamani tu wakati utafutaji unarejesha kitu. match Option Some(atlas) None inachukua hali ya animation, timer, na sprite udhibiti kwa mchezaji. Inategemea ni mstari gani wa atlas inashughulikia mstari wa sasa, snaps kwenye mstari huo wakati mwelekeo unabadilika, na hutumia timer kwa hatua kwa mstari kwa kasi ya kudumu. Wakati harakati huacha sisi kurejesha timer ili animation iko juu ya mstari wa mwisho ulioonyeshwa. animate_player Kuunda Plugin ya Mchezaji // 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)); } } katika Bevy, Plugin ni tu struct ambayo anajua jinsi ya kujiandikisha mifumo, rasilimali, na mali. kwa ajili , tunatoa Bevy orodha ya kuangalia: wakati wowote Plugin hii inajumuishwa kwenye programu, kuendesha msimbo ndani ya kuweka kila kitu kipengele cha mchezaji kinachohitaji. kutoka kuwa tangle ya wito wa mchezaji maalum. PlayerPlugin Plugin PlayerPlugin main.rs ya Mfumo ni orodha ya kuangalia.Bevy hutoa mutable , na tunashughulikia mifumo ambayo tunachohitaji. imepangwa katika hivyo sprite inaonekana mara tu mchezo huanza. na ya Nenda kwenye mipango ili waweze kutekeleza kila frame—kutumia input na animation katika lockstep. na kila kitu kilichotangazwa hapa, kupoteza Katika moja kwa moja inapunguza mtiririko wa mchezaji wote. 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? ya ya ya ya inasema "kila Plugin inapaswa kutoa kazi.” Sisi kutekeleza sifa hiyo, hivyo Rust inatarajia sisi kutoa mwili. Bevy huita mbinu hii wakati inashusha Plugin, ambayo ni kwa nini sisi kuongeza mifumo yetu yote ndani yake. Plugin build(&self, app: &mut App) What’s a trait? Njia ni mkataba unaoelezea mbinu ambazo aina inapaswa kutoa. Maana anasema, “Nendeni mkawekea kufanya kazi ili ningeweza kurekodi mifumo yako.”Kwa kutekeleza kipengele hiki kwa , tunashikilia katika mchakato wa uanzishaji wa Bevy na kuingiza msimbo wetu wa uanzishaji. tabia inaruhusu aina tofauti kushiriki tabia, inafanya kazi kama Plugin nyingine yoyote ya Bevy, lakini huweka mifumo yetu ya mchezaji maalum. Plugin build PlayerPlugin PlayerPlugin Ushirikiano wa mwisho // 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(); } Hebu tuendane na hilo. cargo run Makala hii ilichapishwa awali kwenye blogu yangu. Makala hii ilichapishwa awali kwenye . Blogu yetu Jifunze na mimi kwenye LinkedIn, kufuata mimi kwenye X kwa updates ya hivi karibuni. Maelezo ya LinkedIn X