├── .gitignore ├── LICENSE ├── README.md ├── tutorial1-character-controller ├── .gitignore ├── Cargo.toml ├── data │ ├── models │ │ ├── barrel.FBX │ │ ├── barrel.jpg │ │ ├── barrel_normal.jpg │ │ └── scene.rgs │ └── textures │ │ ├── floor.jpg │ │ └── skybox │ │ ├── back.jpg │ │ ├── down.jpg │ │ ├── front.jpg │ │ ├── left.jpg │ │ ├── right.jpg │ │ └── up.jpg └── src │ └── main.rs ├── tutorial2-weapons ├── .gitignore ├── Cargo.toml ├── data │ ├── models │ │ ├── M4_Body.png │ │ ├── M4_Body_normal.png │ │ ├── M4_Magazine.png │ │ ├── M4_Magazine_normal.png │ │ ├── M4_Sight.png │ │ ├── M4_Sight_normal.png │ │ ├── barrel.FBX │ │ ├── barrel.jpg │ │ ├── barrel_normal.jpg │ │ ├── m4.FBX │ │ └── scene.rgs │ └── textures │ │ ├── floor.jpg │ │ ├── skybox │ │ ├── back.jpg │ │ ├── down.jpg │ │ ├── front.jpg │ │ ├── left.jpg │ │ ├── right.jpg │ │ └── up.jpg │ │ └── spark.png ├── rg3d.log └── src │ ├── main.rs │ ├── message.rs │ └── weapon.rs └── tutorial3-bots-ai ├── .gitignore ├── Cargo.toml ├── data ├── animations │ ├── zombie_attack.fbx │ ├── zombie_idle.fbx │ └── zombie_walk.fbx ├── models │ ├── Ch10_1001_Diffuse.png │ ├── Ch10_1001_Glossiness.png │ ├── Ch10_1001_Normal.png │ ├── Ch10_1001_Specular.png │ ├── Ch10_1002_Diffuse.png │ ├── Ch10_1002_Glossiness.png │ ├── Ch10_1002_Normal.png │ ├── M4_Body.png │ ├── M4_Body_normal.png │ ├── M4_Magazine.png │ ├── M4_Magazine_normal.png │ ├── M4_Sight.png │ ├── M4_Sight_normal.png │ ├── barrel.FBX │ ├── barrel.jpg │ ├── barrel_normal.jpg │ ├── m4.FBX │ ├── scene.rgs │ └── zombie.fbx └── textures │ ├── floor.jpg │ ├── skybox │ ├── back.jpg │ ├── down.jpg │ ├── front.jpg │ ├── left.jpg │ ├── right.jpg │ └── up.jpg │ └── spark.png └── src ├── bot.rs ├── main.rs ├── message.rs └── weapon.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | *.idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Dmitry Stepanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fyrox-tutorials 2 | 3 | ** WARNING **: Source code for tutorials [was moved to the book](https://github.com/fyrox-book/fyrox-book.github.io/tree/main/src/code/tutorials). 4 | 5 | Source code of tutorials for Fyrox Game Engine (formerly known as rg3d) 6 | -------------------------------------------------------------------------------- /tutorial1-character-controller/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | *.idea 13 | *.log -------------------------------------------------------------------------------- /tutorial1-character-controller/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tutorial-shooter" 3 | version = "0.2.0" 4 | authors = ["Dmitry Stepanov "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | fyrox = { path = "../../fyrox", version = "0.29" } -------------------------------------------------------------------------------- /tutorial1-character-controller/data/models/barrel.FBX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial1-character-controller/data/models/barrel.FBX -------------------------------------------------------------------------------- /tutorial1-character-controller/data/models/barrel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial1-character-controller/data/models/barrel.jpg -------------------------------------------------------------------------------- /tutorial1-character-controller/data/models/barrel_normal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial1-character-controller/data/models/barrel_normal.jpg -------------------------------------------------------------------------------- /tutorial1-character-controller/data/models/scene.rgs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial1-character-controller/data/models/scene.rgs -------------------------------------------------------------------------------- /tutorial1-character-controller/data/textures/floor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial1-character-controller/data/textures/floor.jpg -------------------------------------------------------------------------------- /tutorial1-character-controller/data/textures/skybox/back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial1-character-controller/data/textures/skybox/back.jpg -------------------------------------------------------------------------------- /tutorial1-character-controller/data/textures/skybox/down.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial1-character-controller/data/textures/skybox/down.jpg -------------------------------------------------------------------------------- /tutorial1-character-controller/data/textures/skybox/front.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial1-character-controller/data/textures/skybox/front.jpg -------------------------------------------------------------------------------- /tutorial1-character-controller/data/textures/skybox/left.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial1-character-controller/data/textures/skybox/left.jpg -------------------------------------------------------------------------------- /tutorial1-character-controller/data/textures/skybox/right.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial1-character-controller/data/textures/skybox/right.jpg -------------------------------------------------------------------------------- /tutorial1-character-controller/data/textures/skybox/up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial1-character-controller/data/textures/skybox/up.jpg -------------------------------------------------------------------------------- /tutorial1-character-controller/src/main.rs: -------------------------------------------------------------------------------- 1 | use fyrox::{ 2 | core::{ 3 | algebra::{UnitQuaternion, Vector3}, 4 | pool::Handle, 5 | }, 6 | engine::{resource_manager::ResourceManager, Engine, EngineInitParams, SerializationContext}, 7 | event::{DeviceEvent, ElementState, Event, VirtualKeyCode, WindowEvent}, 8 | event_loop::{ControlFlow, EventLoop}, 9 | resource::texture::TextureWrapMode, 10 | scene::{ 11 | base::BaseBuilder, 12 | camera::{CameraBuilder, SkyBox, SkyBoxBuilder}, 13 | collider::{ColliderBuilder, ColliderShape}, 14 | node::Node, 15 | rigidbody::RigidBodyBuilder, 16 | transform::TransformBuilder, 17 | Scene, 18 | }, 19 | window::WindowBuilder, 20 | }; 21 | use std::{sync::Arc, time}; 22 | 23 | // Our game logic will be updated at 60 Hz rate. 24 | const TIMESTEP: f32 = 1.0 / 60.0; 25 | 26 | #[derive(Default)] 27 | struct InputController { 28 | move_forward: bool, 29 | move_backward: bool, 30 | move_left: bool, 31 | move_right: bool, 32 | pitch: f32, 33 | yaw: f32, 34 | } 35 | 36 | struct Player { 37 | camera: Handle, 38 | rigid_body: Handle, 39 | controller: InputController, 40 | } 41 | 42 | async fn create_skybox(resource_manager: ResourceManager) -> SkyBox { 43 | // Load skybox textures in parallel. 44 | let (front, back, left, right, top, bottom) = fyrox::core::futures::join!( 45 | resource_manager.request_texture("data/textures/skybox/front.jpg"), 46 | resource_manager.request_texture("data/textures/skybox/back.jpg"), 47 | resource_manager.request_texture("data/textures/skybox/left.jpg"), 48 | resource_manager.request_texture("data/textures/skybox/right.jpg"), 49 | resource_manager.request_texture("data/textures/skybox/up.jpg"), 50 | resource_manager.request_texture("data/textures/skybox/down.jpg") 51 | ); 52 | 53 | // Unwrap everything. 54 | let skybox = SkyBoxBuilder { 55 | front: Some(front.unwrap()), 56 | back: Some(back.unwrap()), 57 | left: Some(left.unwrap()), 58 | right: Some(right.unwrap()), 59 | top: Some(top.unwrap()), 60 | bottom: Some(bottom.unwrap()), 61 | } 62 | .build() 63 | .unwrap(); 64 | 65 | // Set S and T coordinate wrap mode, ClampToEdge will remove any possible seams on edges 66 | // of the skybox. 67 | let skybox_texture = skybox.cubemap().unwrap(); 68 | let mut data = skybox_texture.data_ref(); 69 | data.set_s_wrap_mode(TextureWrapMode::ClampToEdge); 70 | data.set_t_wrap_mode(TextureWrapMode::ClampToEdge); 71 | 72 | skybox 73 | } 74 | 75 | impl Player { 76 | async fn new(scene: &mut Scene, resource_manager: ResourceManager) -> Self { 77 | // Create rigid body with a camera, move it a bit up to "emulate" head. 78 | let camera; 79 | let rigid_body_handle = RigidBodyBuilder::new( 80 | BaseBuilder::new() 81 | .with_local_transform( 82 | TransformBuilder::new() 83 | // Offset player a bit. 84 | .with_local_position(Vector3::new(0.0, 1.0, -1.0)) 85 | .build(), 86 | ) 87 | .with_children(&[ 88 | { 89 | camera = CameraBuilder::new( 90 | BaseBuilder::new().with_local_transform( 91 | TransformBuilder::new() 92 | .with_local_position(Vector3::new(0.0, 0.25, 0.0)) 93 | .build(), 94 | ), 95 | ) 96 | .with_skybox(create_skybox(resource_manager).await) 97 | .build(&mut scene.graph); 98 | camera 99 | }, 100 | // Add capsule collider for the rigid body. 101 | ColliderBuilder::new(BaseBuilder::new()) 102 | .with_shape(ColliderShape::capsule_y(0.25, 0.2)) 103 | .build(&mut scene.graph), 104 | ]), 105 | ) 106 | // We don't want the player to tilt. 107 | .with_locked_rotations(true) 108 | // We don't want the rigid body to sleep (be excluded from simulation) 109 | .with_can_sleep(false) 110 | .build(&mut scene.graph); 111 | 112 | Self { 113 | camera, 114 | rigid_body: rigid_body_handle, 115 | controller: Default::default(), 116 | } 117 | } 118 | 119 | fn update(&mut self, scene: &mut Scene) { 120 | // Set pitch for the camera. These lines responsible for up-down camera rotation. 121 | scene.graph[self.camera].local_transform_mut().set_rotation( 122 | UnitQuaternion::from_axis_angle(&Vector3::x_axis(), self.controller.pitch.to_radians()), 123 | ); 124 | 125 | // Borrow rigid body node. 126 | let body = scene.graph[self.rigid_body].as_rigid_body_mut(); 127 | 128 | // Keep only vertical velocity, and drop horizontal. 129 | let mut velocity = Vector3::new(0.0, body.lin_vel().y, 0.0); 130 | 131 | // Change the velocity depending on the keys pressed. 132 | if self.controller.move_forward { 133 | // If we moving forward then add "look" vector of the body. 134 | velocity += body.look_vector(); 135 | } 136 | if self.controller.move_backward { 137 | // If we moving backward then subtract "look" vector of the body. 138 | velocity -= body.look_vector(); 139 | } 140 | if self.controller.move_left { 141 | // If we moving left then add "side" vector of the body. 142 | velocity += body.side_vector(); 143 | } 144 | if self.controller.move_right { 145 | // If we moving right then subtract "side" vector of the body. 146 | velocity -= body.side_vector(); 147 | } 148 | 149 | // Finally new linear velocity. 150 | body.set_lin_vel(velocity); 151 | 152 | // Change the rotation of the rigid body according to current yaw. These lines responsible for 153 | // left-right rotation. 154 | body.local_transform_mut() 155 | .set_rotation(UnitQuaternion::from_axis_angle( 156 | &Vector3::y_axis(), 157 | self.controller.yaw.to_radians(), 158 | )); 159 | } 160 | 161 | fn process_input_event(&mut self, event: &Event<()>) { 162 | match event { 163 | Event::WindowEvent { event, .. } => { 164 | if let WindowEvent::KeyboardInput { input, .. } = event { 165 | if let Some(key_code) = input.virtual_keycode { 166 | match key_code { 167 | VirtualKeyCode::W => { 168 | self.controller.move_forward = input.state == ElementState::Pressed; 169 | } 170 | VirtualKeyCode::S => { 171 | self.controller.move_backward = 172 | input.state == ElementState::Pressed; 173 | } 174 | VirtualKeyCode::A => { 175 | self.controller.move_left = input.state == ElementState::Pressed; 176 | } 177 | VirtualKeyCode::D => { 178 | self.controller.move_right = input.state == ElementState::Pressed; 179 | } 180 | _ => (), 181 | } 182 | } 183 | } 184 | } 185 | Event::DeviceEvent { event, .. } => { 186 | if let DeviceEvent::MouseMotion { delta } = event { 187 | self.controller.yaw -= delta.0 as f32; 188 | 189 | self.controller.pitch = 190 | (self.controller.pitch + delta.1 as f32).clamp(-90.0, 90.0); 191 | } 192 | } 193 | _ => (), 194 | } 195 | } 196 | } 197 | 198 | struct Game { 199 | scene: Handle, 200 | player: Player, 201 | } 202 | 203 | impl Game { 204 | pub async fn new(engine: &mut Engine) -> Self { 205 | let mut scene = Scene::new(); 206 | 207 | // Load a scene resource and create its instance. 208 | engine 209 | .resource_manager 210 | .request_model("data/models/scene.rgs") 211 | .await 212 | .unwrap() 213 | .instantiate(&mut scene); 214 | 215 | Self { 216 | player: Player::new(&mut scene, engine.resource_manager.clone()).await, 217 | scene: engine.scenes.add(scene), 218 | } 219 | } 220 | 221 | pub fn update(&mut self, engine: &mut Engine) { 222 | self.player.update(&mut engine.scenes[self.scene]); 223 | } 224 | } 225 | 226 | fn main() { 227 | // Configure main window first. 228 | let window_builder = WindowBuilder::new().with_title("3D Shooter Tutorial"); 229 | // Create event loop that will be used to "listen" events from the OS. 230 | let event_loop = EventLoop::new(); 231 | 232 | // Finally create an instance of the engine. 233 | let serialization_context = Arc::new(SerializationContext::new()); 234 | let mut engine = Engine::new(EngineInitParams { 235 | window_builder, 236 | resource_manager: ResourceManager::new(serialization_context.clone()), 237 | serialization_context, 238 | events_loop: &event_loop, 239 | vsync: false, 240 | headless: false, 241 | }) 242 | .unwrap(); 243 | 244 | // Initialize game instance. 245 | let mut game = fyrox::core::futures::executor::block_on(Game::new(&mut engine)); 246 | 247 | // Run the event loop of the main window. which will respond to OS and window events and update 248 | // engine's state accordingly. Engine lets you to decide which event should be handled, 249 | // this is minimal working example if how it should be. 250 | let mut previous = time::Instant::now(); 251 | let mut lag = 0.0; 252 | event_loop.run(move |event, _, control_flow| { 253 | game.player.process_input_event(&event); 254 | 255 | match event { 256 | Event::MainEventsCleared => { 257 | // This main game loop - it has fixed time step which means that game 258 | // code will run at fixed speed even if renderer can't give you desired 259 | // 60 fps. 260 | let elapsed = previous.elapsed(); 261 | previous = time::Instant::now(); 262 | lag += elapsed.as_secs_f32(); 263 | while lag >= TIMESTEP { 264 | lag -= TIMESTEP; 265 | 266 | // Run our game's logic. 267 | game.update(&mut engine); 268 | 269 | // Update engine each frame. 270 | engine.update(TIMESTEP, control_flow, &mut lag, Default::default()); 271 | } 272 | 273 | // Rendering must be explicitly requested and handled after RedrawRequested event is received. 274 | engine.get_window().request_redraw(); 275 | } 276 | Event::RedrawRequested(_) => { 277 | // Render at max speed - it is not tied to the game code. 278 | engine.render().unwrap(); 279 | } 280 | Event::WindowEvent { event, .. } => match event { 281 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 282 | WindowEvent::KeyboardInput { input, .. } => { 283 | // Exit game by hitting Escape. 284 | if let Some(VirtualKeyCode::Escape) = input.virtual_keycode { 285 | *control_flow = ControlFlow::Exit 286 | } 287 | } 288 | WindowEvent::Resized(size) => { 289 | // It is very important to handle Resized event from window, because 290 | // renderer knows nothing about window size - it must be notified 291 | // directly when window size has changed. 292 | engine.set_frame_size(size.into()).unwrap(); 293 | } 294 | _ => (), 295 | }, 296 | _ => *control_flow = ControlFlow::Poll, 297 | } 298 | }); 299 | } 300 | -------------------------------------------------------------------------------- /tutorial2-weapons/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | *.idea 13 | *.log 14 | -------------------------------------------------------------------------------- /tutorial2-weapons/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tutorial-shooter" 3 | version = "0.2.0" 4 | authors = ["Dmitry Stepanov "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | fyrox = { path = "../../fyrox", version = "0.29" } -------------------------------------------------------------------------------- /tutorial2-weapons/data/models/M4_Body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/models/M4_Body.png -------------------------------------------------------------------------------- /tutorial2-weapons/data/models/M4_Body_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/models/M4_Body_normal.png -------------------------------------------------------------------------------- /tutorial2-weapons/data/models/M4_Magazine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/models/M4_Magazine.png -------------------------------------------------------------------------------- /tutorial2-weapons/data/models/M4_Magazine_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/models/M4_Magazine_normal.png -------------------------------------------------------------------------------- /tutorial2-weapons/data/models/M4_Sight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/models/M4_Sight.png -------------------------------------------------------------------------------- /tutorial2-weapons/data/models/M4_Sight_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/models/M4_Sight_normal.png -------------------------------------------------------------------------------- /tutorial2-weapons/data/models/barrel.FBX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/models/barrel.FBX -------------------------------------------------------------------------------- /tutorial2-weapons/data/models/barrel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/models/barrel.jpg -------------------------------------------------------------------------------- /tutorial2-weapons/data/models/barrel_normal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/models/barrel_normal.jpg -------------------------------------------------------------------------------- /tutorial2-weapons/data/models/m4.FBX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/models/m4.FBX -------------------------------------------------------------------------------- /tutorial2-weapons/data/models/scene.rgs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/models/scene.rgs -------------------------------------------------------------------------------- /tutorial2-weapons/data/textures/floor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/textures/floor.jpg -------------------------------------------------------------------------------- /tutorial2-weapons/data/textures/skybox/back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/textures/skybox/back.jpg -------------------------------------------------------------------------------- /tutorial2-weapons/data/textures/skybox/down.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/textures/skybox/down.jpg -------------------------------------------------------------------------------- /tutorial2-weapons/data/textures/skybox/front.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/textures/skybox/front.jpg -------------------------------------------------------------------------------- /tutorial2-weapons/data/textures/skybox/left.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/textures/skybox/left.jpg -------------------------------------------------------------------------------- /tutorial2-weapons/data/textures/skybox/right.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/textures/skybox/right.jpg -------------------------------------------------------------------------------- /tutorial2-weapons/data/textures/skybox/up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/textures/skybox/up.jpg -------------------------------------------------------------------------------- /tutorial2-weapons/data/textures/spark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial2-weapons/data/textures/spark.png -------------------------------------------------------------------------------- /tutorial2-weapons/rg3d.log: -------------------------------------------------------------------------------- 1 | [INFO]: Shader BlurShader_VertexShader compiled successfully! 2 | [INFO]: Shader BlurShader_FragmentShader compiled successfully! 3 | [INFO]: Shader BlurShader linked successfully! 4 | [INFO]: Shader SsaoShader_VertexShader compiled successfully! 5 | [INFO]: Shader SsaoShader_FragmentShader compiled successfully! 6 | [INFO]: Shader SsaoShader linked successfully! 7 | [INFO]: Shader SpotLightShader_VertexShader compiled successfully! 8 | [INFO]: Shader SpotLightShader_FragmentShader compiled successfully! 9 | [INFO]: Shader SpotLightShader linked successfully! 10 | [INFO]: Shader PointLightShader_VertexShader compiled successfully! 11 | [INFO]: Shader PointLightShader_FragmentShader compiled successfully! 12 | [INFO]: Shader PointLightShader linked successfully! 13 | [INFO]: Shader DirectionalLightShader_VertexShader compiled successfully! 14 | [INFO]: Shader DirectionalLightShader_FragmentShader compiled successfully! 15 | [INFO]: Shader DirectionalLightShader linked successfully! 16 | [INFO]: Shader AmbientLightShader_VertexShader compiled successfully! 17 | [INFO]: Shader AmbientLightShader_FragmentShader compiled successfully! 18 | [INFO]: Shader AmbientLightShader linked successfully! 19 | [INFO]: Shader FlatShader_VertexShader compiled successfully! 20 | [INFO]: Shader FlatShader_FragmentShader compiled successfully! 21 | [INFO]: Shader FlatShader linked successfully! 22 | [INFO]: Shader SkyboxShader_VertexShader compiled successfully! 23 | [INFO]: Shader SkyboxShader_FragmentShader compiled successfully! 24 | [INFO]: Shader SkyboxShader linked successfully! 25 | [INFO]: Shader SpotVolumetricLight_VertexShader compiled successfully! 26 | [INFO]: Shader SpotVolumetricLight_FragmentShader compiled successfully! 27 | [INFO]: Shader SpotVolumetricLight linked successfully! 28 | [INFO]: Shader PointVolumetricLight_VertexShader compiled successfully! 29 | [INFO]: Shader PointVolumetricLight_FragmentShader compiled successfully! 30 | [INFO]: Shader PointVolumetricLight linked successfully! 31 | [INFO]: Shader FlatShader_VertexShader compiled successfully! 32 | [INFO]: Shader FlatShader_FragmentShader compiled successfully! 33 | [INFO]: Shader FlatShader linked successfully! 34 | [INFO]: Shader FlatShader_VertexShader compiled successfully! 35 | [INFO]: Shader FlatShader_FragmentShader compiled successfully! 36 | [INFO]: Shader FlatShader linked successfully! 37 | [INFO]: Shader SpriteShader_VertexShader compiled successfully! 38 | [INFO]: Shader SpriteShader_FragmentShader compiled successfully! 39 | [INFO]: Shader SpriteShader linked successfully! 40 | [INFO]: Shader UIShader_VertexShader compiled successfully! 41 | [INFO]: Shader UIShader_FragmentShader compiled successfully! 42 | [INFO]: Shader UIShader linked successfully! 43 | [INFO]: Shader ParticleSystemShader_VertexShader compiled successfully! 44 | [INFO]: Shader ParticleSystemShader_FragmentShader compiled successfully! 45 | [INFO]: Shader ParticleSystemShader linked successfully! 46 | [INFO]: Shader DebugShader_VertexShader compiled successfully! 47 | [INFO]: Shader DebugShader_FragmentShader compiled successfully! 48 | [INFO]: Shader DebugShader linked successfully! 49 | [INFO]: Shader FXAAShader_VertexShader compiled successfully! 50 | [INFO]: Shader FXAAShader_FragmentShader compiled successfully! 51 | [INFO]: Shader FXAAShader linked successfully! 52 | [INFO]: Shader RectangleShader_VertexShader compiled successfully! 53 | [INFO]: Shader RectangleShader_FragmentShader compiled successfully! 54 | [INFO]: Shader RectangleShader linked successfully! 55 | [WARNING]: Unable to load options file data/models/scene.rgs.options for data/models/scene.rgs resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 56 | [WARNING]: Unable to load options file data/models/barrel.FBX.options for data/models/barrel.FBX resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 57 | [INFO]: Trying to load "data/models/barrel.FBX" 58 | [INFO]: FBX "data/models/barrel.FBX" loaded in 0 ms 59 | - Parsing - 0 ms 60 | - DOM Prepare - 0 ms 61 | - Conversion - 0 ms 62 | [INFO]: Model "data/models/barrel.FBX" is loaded! 63 | [WARNING]: Unable to load options file data/models\barrel.jpg.options for data/models\barrel.jpg resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 64 | [WARNING]: Unable to load options file data/models\barrel_normal.jpg.options for data/models\barrel_normal.jpg resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 65 | [INFO]: Starting resolve... 66 | [INFO]: Resolving graph... 67 | [INFO]: Original handles resolved! 68 | [INFO]: Checking integrity... 69 | [INFO]: Integrity restored for 3 instances! 0 new nodes were added! 70 | [INFO]: Graph resolved successfully! 71 | [INFO]: Resolving animations... 72 | [INFO]: Animations resolved successfully! 73 | [WARNING]: Unable to load options file data/textures/floor.jpg.options for data/textures/floor.jpg resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 74 | [INFO]: Resolve succeeded! 75 | [INFO]: Model "data/models/scene.rgs" is loaded! 76 | [WARNING]: Unable to load options file data/textures/skybox/front.jpg.options for data/textures/skybox/front.jpg resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 77 | [WARNING]: Unable to load options file data/textures/skybox/back.jpg.options for data/textures/skybox/back.jpg resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 78 | [WARNING]: Unable to load options file data/textures/skybox/right.jpg.options for data/textures/skybox/right.jpg resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 79 | [WARNING]: Unable to load options file data/textures/skybox/left.jpg.options for data/textures/skybox/left.jpg resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 80 | [WARNING]: Unable to load options file data/textures/skybox/down.jpg.options for data/textures/skybox/down.jpg resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 81 | [WARNING]: Unable to load options file data/textures/skybox/up.jpg.options for data/textures/skybox/up.jpg resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 82 | [INFO]: Texture "data/models\\barrel.jpg" is loaded in 82.0416ms! 83 | [INFO]: Texture "data/models\\barrel_normal.jpg" is loaded in 87.1124ms! 84 | [INFO]: Texture "data/textures/skybox/up.jpg" is loaded in 207.3779ms! 85 | [INFO]: Texture "data/textures/floor.jpg" is loaded in 220.9285ms! 86 | [INFO]: Texture "data/textures/skybox/down.jpg" is loaded in 219.8154ms! 87 | [INFO]: Texture "data/textures/skybox/right.jpg" is loaded in 240.938ms! 88 | [INFO]: Texture "data/textures/skybox/front.jpg" is loaded in 260.4928ms! 89 | [INFO]: Texture "data/textures/skybox/back.jpg" is loaded in 271.6325ms! 90 | [INFO]: Texture "data/textures/skybox/left.jpg" is loaded in 278.5044ms! 91 | [WARNING]: Unable to load options file data/models/m4.FBX.options for data/models/m4.FBX resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 92 | [INFO]: Trying to load "data/models/m4.FBX" 93 | [WARNING]: Unable to load options file data/models\M4_Magazine.png.options for data/models\M4_Magazine.png resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 94 | [WARNING]: Unable to load options file data/models\M4_Magazine_normal.png.options for data/models\M4_Magazine_normal.png resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 95 | [WARNING]: Unable to load options file data/models\M4_Sight.png.options for data/models\M4_Sight.png resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 96 | [WARNING]: Unable to load options file data/models\M4_Sight_normal.png.options for data/models\M4_Sight_normal.png resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 97 | [WARNING]: Unable to load options file data/models\M4_Body.png.options for data/models\M4_Body.png resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 98 | [WARNING]: Unable to load options file data/models\M4_Body_normal.png.options for data/models\M4_Body_normal.png resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 99 | [INFO]: FBX "data/models/m4.FBX" loaded in 23 ms 100 | - Parsing - 15 ms 101 | - DOM Prepare - 1 ms 102 | - Conversion - 6 ms 103 | [INFO]: Model "data/models/m4.FBX" is loaded! 104 | [INFO]: Shader BlurShader_VertexShader compiled successfully! 105 | [INFO]: Shader BlurShader_FragmentShader compiled successfully! 106 | [INFO]: Shader BlurShader linked successfully! 107 | [INFO]: Shader SsaoShader_VertexShader compiled successfully! 108 | [INFO]: Shader SsaoShader_FragmentShader compiled successfully! 109 | [INFO]: Shader SsaoShader linked successfully! 110 | [INFO]: Shader BlurShader_VertexShader compiled successfully! 111 | [INFO]: Shader BlurShader_FragmentShader compiled successfully! 112 | [INFO]: Shader BlurShader linked successfully! 113 | [INFO]: Shader SsaoShader_VertexShader compiled successfully! 114 | [INFO]: Shader SsaoShader_FragmentShader compiled successfully! 115 | [INFO]: Shader SsaoShader linked successfully! 116 | [INFO]: Shader BlurShader_VertexShader compiled successfully! 117 | [INFO]: Shader BlurShader_FragmentShader compiled successfully! 118 | [INFO]: Shader BlurShader linked successfully! 119 | [INFO]: Shader SsaoShader_VertexShader compiled successfully! 120 | [INFO]: Shader SsaoShader_FragmentShader compiled successfully! 121 | [INFO]: Shader SsaoShader linked successfully! 122 | [INFO]: Shader BlurShader_VertexShader compiled successfully! 123 | [INFO]: Shader BlurShader_FragmentShader compiled successfully! 124 | [INFO]: Shader BlurShader linked successfully! 125 | [INFO]: Shader SsaoShader_VertexShader compiled successfully! 126 | [INFO]: Shader SsaoShader_FragmentShader compiled successfully! 127 | [INFO]: Shader SsaoShader linked successfully! 128 | [INFO]: Native rigid body was created for node Rigid Body 129 | [INFO]: Native collider was created for node Collider 130 | [INFO]: Native rigid body was created for node Rigid Body 131 | [INFO]: Native collider was created for node Collider 132 | [INFO]: Native rigid body was created for node Rigid Body 133 | [INFO]: Native collider was created for node Collider 134 | [INFO]: Native rigid body was created for node Rigid Body 135 | [INFO]: Native collider was created for node Collider 136 | [INFO]: Native rigid body was created for node 137 | [INFO]: Shader DecalShader_VertexShader compiled successfully! 138 | [INFO]: Shader DecalShader_FragmentShader compiled successfully! 139 | [INFO]: Shader DecalShader linked successfully! 140 | [INFO]: Shader AdaptationShader_VertexShader compiled successfully! 141 | [INFO]: Shader AdaptationShader_FragmentShader compiled successfully! 142 | [INFO]: Shader AdaptationShader linked successfully! 143 | [INFO]: Shader LuminanceShader_VertexShader compiled successfully! 144 | [INFO]: Shader LuminanceShader_FragmentShader compiled successfully! 145 | [INFO]: Shader LuminanceShader linked successfully! 146 | [INFO]: Shader DownscaleShader_VertexShader compiled successfully! 147 | [INFO]: Shader DownscaleShader_FragmentShader compiled successfully! 148 | [INFO]: Shader DownscaleShader linked successfully! 149 | [INFO]: Shader HdrToLdrShader_VertexShader compiled successfully! 150 | [INFO]: Shader HdrToLdrShader_FragmentShader compiled successfully! 151 | [INFO]: Shader HdrToLdrShader linked successfully! 152 | [INFO]: Shader BloomShader_VertexShader compiled successfully! 153 | [INFO]: Shader BloomShader_FragmentShader compiled successfully! 154 | [INFO]: Shader BloomShader linked successfully! 155 | [INFO]: Shader GaussianBlurShader_VertexShader compiled successfully! 156 | [INFO]: Shader GaussianBlurShader_FragmentShader compiled successfully! 157 | [INFO]: Shader GaussianBlurShader linked successfully! 158 | [INFO]: Shader StandardShader_GBuffer_VertexShader compiled successfully! 159 | [INFO]: Shader StandardShader_GBuffer_FragmentShader compiled successfully! 160 | [INFO]: Shader StandardShader_GBuffer linked successfully! 161 | [INFO]: Shader StandardShader_Forward_VertexShader compiled successfully! 162 | [INFO]: Shader StandardShader_Forward_FragmentShader compiled successfully! 163 | [INFO]: Shader StandardShader_Forward linked successfully! 164 | [INFO]: Shader StandardShader_DirectionalShadow_VertexShader compiled successfully! 165 | [INFO]: Shader StandardShader_DirectionalShadow_FragmentShader compiled successfully! 166 | [INFO]: Shader StandardShader_DirectionalShadow linked successfully! 167 | [INFO]: Shader StandardShader_SpotShadow_VertexShader compiled successfully! 168 | [INFO]: Shader StandardShader_SpotShadow_FragmentShader compiled successfully! 169 | [INFO]: Shader StandardShader_SpotShadow linked successfully! 170 | [INFO]: Shader StandardShader_PointShadow_VertexShader compiled successfully! 171 | [INFO]: Shader StandardShader_PointShadow_FragmentShader compiled successfully! 172 | [INFO]: Shader StandardShader_PointShadow linked successfully! 173 | [INFO]: Shader StandardShader_GBuffer_VertexShader compiled successfully! 174 | [INFO]: Shader StandardShader_GBuffer_FragmentShader compiled successfully! 175 | [INFO]: Shader StandardShader_GBuffer linked successfully! 176 | [INFO]: Shader StandardShader_Forward_VertexShader compiled successfully! 177 | [INFO]: Shader StandardShader_Forward_FragmentShader compiled successfully! 178 | [INFO]: Shader StandardShader_Forward linked successfully! 179 | [INFO]: Shader StandardShader_DirectionalShadow_VertexShader compiled successfully! 180 | [INFO]: Shader StandardShader_DirectionalShadow_FragmentShader compiled successfully! 181 | [INFO]: Shader StandardShader_DirectionalShadow linked successfully! 182 | [INFO]: Shader StandardShader_SpotShadow_VertexShader compiled successfully! 183 | [INFO]: Shader StandardShader_SpotShadow_FragmentShader compiled successfully! 184 | [INFO]: Shader StandardShader_SpotShadow linked successfully! 185 | [INFO]: Shader StandardShader_PointShadow_VertexShader compiled successfully! 186 | [INFO]: Shader StandardShader_PointShadow_FragmentShader compiled successfully! 187 | [INFO]: Shader StandardShader_PointShadow linked successfully! 188 | [INFO]: Texture "data/models\\M4_Magazine_normal.png" is loaded in 284.3637ms! 189 | [INFO]: Texture "data/models\\M4_Sight.png" is loaded in 285.6951ms! 190 | [INFO]: Texture "data/models\\M4_Magazine.png" is loaded in 288.4077ms! 191 | [INFO]: Texture "data/models\\M4_Sight_normal.png" is loaded in 290.4226ms! 192 | [INFO]: Texture "data/models\\M4_Body.png" is loaded in 298.4835ms! 193 | [INFO]: Texture "data/models\\M4_Body_normal.png" is loaded in 302.71ms! 194 | [INFO]: Native collider was created for node 195 | [WARNING]: Unable to load options file data/textures/spark.png.options for data/textures/spark.png resource, fallback to defaults! Reason: Io(Os { code: 2, kind: NotFound, message: "Не удается найти указанный файл." }) 196 | [INFO]: Texture "data/textures/spark.png" is loaded in 35.9675ms! 197 | -------------------------------------------------------------------------------- /tutorial2-weapons/src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::{message::Message, weapon::Weapon}; 2 | use fyrox::{ 3 | core::{ 4 | algebra::{Point3, UnitQuaternion, Vector3}, 5 | color::Color, 6 | color_gradient::{ColorGradient, GradientPoint}, 7 | math::ray::Ray, 8 | math::vector_to_quat, 9 | pool::{Handle, Pool}, 10 | sstorage::ImmutableString, 11 | }, 12 | engine::{resource_manager::ResourceManager, Engine, EngineInitParams, SerializationContext}, 13 | event::{DeviceEvent, ElementState, Event, MouseButton, VirtualKeyCode, WindowEvent}, 14 | event_loop::{ControlFlow, EventLoop}, 15 | material::{Material, PropertyValue, SharedMaterial}, 16 | resource::texture::TextureWrapMode, 17 | scene::{ 18 | base::BaseBuilder, 19 | camera::{CameraBuilder, SkyBox, SkyBoxBuilder}, 20 | collider::{ColliderBuilder, ColliderShape}, 21 | graph::{physics::RayCastOptions, Graph}, 22 | mesh::{ 23 | surface::{SurfaceBuilder, SurfaceData, SurfaceSharedData}, 24 | MeshBuilder, RenderPath, 25 | }, 26 | node::Node, 27 | particle_system::{ 28 | emitter::{base::BaseEmitterBuilder, sphere::SphereEmitterBuilder}, 29 | ParticleSystemBuilder, 30 | }, 31 | pivot::PivotBuilder, 32 | rigidbody::RigidBodyBuilder, 33 | transform::TransformBuilder, 34 | Scene, 35 | }, 36 | window::WindowBuilder, 37 | }; 38 | use std::{ 39 | path::Path, 40 | sync::{ 41 | mpsc::{self, Receiver, Sender}, 42 | Arc, 43 | }, 44 | time, 45 | }; 46 | 47 | pub mod message; 48 | pub mod weapon; 49 | 50 | // Our game logic will be updated at 60 Hz rate. 51 | const TIMESTEP: f32 = 1.0 / 60.0; 52 | 53 | #[derive(Default)] 54 | struct InputController { 55 | move_forward: bool, 56 | move_backward: bool, 57 | move_left: bool, 58 | move_right: bool, 59 | pitch: f32, 60 | yaw: f32, 61 | shoot: bool, 62 | } 63 | 64 | struct Player { 65 | camera: Handle, 66 | rigid_body: Handle, 67 | controller: InputController, 68 | weapon_pivot: Handle, 69 | sender: Sender, 70 | weapon: Handle, 71 | collider: Handle, 72 | } 73 | 74 | async fn create_skybox(resource_manager: ResourceManager) -> SkyBox { 75 | // Load skybox textures in parallel. 76 | let (front, back, left, right, top, bottom) = fyrox::core::futures::join!( 77 | resource_manager.request_texture("data/textures/skybox/front.jpg"), 78 | resource_manager.request_texture("data/textures/skybox/back.jpg"), 79 | resource_manager.request_texture("data/textures/skybox/left.jpg"), 80 | resource_manager.request_texture("data/textures/skybox/right.jpg"), 81 | resource_manager.request_texture("data/textures/skybox/up.jpg"), 82 | resource_manager.request_texture("data/textures/skybox/down.jpg") 83 | ); 84 | 85 | // Unwrap everything. 86 | let skybox = SkyBoxBuilder { 87 | front: Some(front.unwrap()), 88 | back: Some(back.unwrap()), 89 | left: Some(left.unwrap()), 90 | right: Some(right.unwrap()), 91 | top: Some(top.unwrap()), 92 | bottom: Some(bottom.unwrap()), 93 | } 94 | .build() 95 | .unwrap(); 96 | 97 | // Set S and T coordinate wrap mode, ClampToEdge will remove any possible seams on edges 98 | // of the skybox. 99 | let skybox_texture = skybox.cubemap().unwrap(); 100 | let mut data = skybox_texture.data_ref(); 101 | data.set_s_wrap_mode(TextureWrapMode::ClampToEdge); 102 | data.set_t_wrap_mode(TextureWrapMode::ClampToEdge); 103 | 104 | skybox 105 | } 106 | 107 | fn create_bullet_impact( 108 | graph: &mut Graph, 109 | resource_manager: ResourceManager, 110 | pos: Vector3, 111 | orientation: UnitQuaternion, 112 | ) -> Handle { 113 | // Create sphere emitter first. 114 | let emitter = SphereEmitterBuilder::new( 115 | BaseEmitterBuilder::new() 116 | .with_max_particles(200) 117 | .with_spawn_rate(3000) 118 | .with_size_modifier_range(-0.01..-0.0125) 119 | .with_size_range(0.0075..0.015) 120 | .with_lifetime_range(0.05..0.2) 121 | .with_x_velocity_range(-0.0075..0.0075) 122 | .with_y_velocity_range(-0.0075..0.0075) 123 | .with_z_velocity_range(0.025..0.045) 124 | .resurrect_particles(false), 125 | ) 126 | .with_radius(0.01) 127 | .build(); 128 | 129 | // Color gradient will be used to modify color of each particle over its lifetime. 130 | let color_gradient = { 131 | let mut gradient = ColorGradient::new(); 132 | gradient.add_point(GradientPoint::new(0.00, Color::from_rgba(255, 255, 0, 0))); 133 | gradient.add_point(GradientPoint::new(0.05, Color::from_rgba(255, 160, 0, 255))); 134 | gradient.add_point(GradientPoint::new(0.95, Color::from_rgba(255, 120, 0, 255))); 135 | gradient.add_point(GradientPoint::new(1.00, Color::from_rgba(255, 60, 0, 0))); 136 | gradient 137 | }; 138 | 139 | // Create new transform to orient and position particle system. 140 | let transform = TransformBuilder::new() 141 | .with_local_position(pos) 142 | .with_local_rotation(orientation) 143 | .build(); 144 | 145 | // Finally create particle system with limited lifetime. 146 | ParticleSystemBuilder::new( 147 | BaseBuilder::new() 148 | .with_lifetime(1.0) 149 | .with_local_transform(transform), 150 | ) 151 | .with_acceleration(Vector3::new(0.0, 0.0, 0.0)) 152 | .with_color_over_lifetime_gradient(color_gradient) 153 | .with_emitters(vec![emitter]) 154 | // We'll use simple spark texture for each particle. 155 | .with_texture(resource_manager.request_texture(Path::new("data/textures/spark.png"))) 156 | .build(graph) 157 | } 158 | 159 | impl Player { 160 | async fn new( 161 | scene: &mut Scene, 162 | resource_manager: ResourceManager, 163 | sender: Sender, 164 | ) -> Self { 165 | // Create rigid body with a camera, move it a bit up to "emulate" head. 166 | let camera; 167 | let weapon_pivot; 168 | let collider; 169 | let rigid_body_handle = RigidBodyBuilder::new( 170 | BaseBuilder::new() 171 | .with_local_transform( 172 | TransformBuilder::new() 173 | // Offset player a bit. 174 | .with_local_position(Vector3::new(0.0, 1.0, -1.0)) 175 | .build(), 176 | ) 177 | .with_children(&[ 178 | { 179 | camera = CameraBuilder::new( 180 | BaseBuilder::new() 181 | .with_local_transform( 182 | TransformBuilder::new() 183 | .with_local_position(Vector3::new(0.0, 0.25, 0.0)) 184 | .build(), 185 | ) 186 | .with_children(&[{ 187 | weapon_pivot = PivotBuilder::new( 188 | BaseBuilder::new().with_local_transform( 189 | TransformBuilder::new() 190 | .with_local_position(Vector3::new( 191 | -0.1, -0.05, 0.015, 192 | )) 193 | .build(), 194 | ), 195 | ) 196 | .build(&mut scene.graph); 197 | weapon_pivot 198 | }]), 199 | ) 200 | .with_skybox(create_skybox(resource_manager).await) 201 | .build(&mut scene.graph); 202 | camera 203 | }, 204 | // Add capsule collider for the rigid body. 205 | { 206 | collider = ColliderBuilder::new(BaseBuilder::new()) 207 | .with_shape(ColliderShape::capsule_y(0.25, 0.2)) 208 | .build(&mut scene.graph); 209 | collider 210 | }, 211 | ]), 212 | ) 213 | // We don't want the player to tilt. 214 | .with_locked_rotations(true) 215 | // We don't want the rigid body to sleep (be excluded from simulation) 216 | .with_can_sleep(false) 217 | .build(&mut scene.graph); 218 | 219 | Self { 220 | camera, 221 | weapon_pivot, 222 | rigid_body: rigid_body_handle, 223 | controller: Default::default(), 224 | sender, 225 | collider, 226 | weapon: Default::default(), // Leave it unassigned for now. 227 | } 228 | } 229 | 230 | fn update(&mut self, scene: &mut Scene) { 231 | // Set pitch for the camera. These lines responsible for up-down camera rotation. 232 | scene.graph[self.camera].local_transform_mut().set_rotation( 233 | UnitQuaternion::from_axis_angle(&Vector3::x_axis(), self.controller.pitch.to_radians()), 234 | ); 235 | 236 | // Borrow rigid body node. 237 | let body = scene.graph[self.rigid_body].as_rigid_body_mut(); 238 | 239 | // Keep only vertical velocity, and drop horizontal. 240 | let mut velocity = Vector3::new(0.0, body.lin_vel().y, 0.0); 241 | 242 | // Change the velocity depending on the keys pressed. 243 | if self.controller.move_forward { 244 | // If we moving forward then add "look" vector of the body. 245 | velocity += body.look_vector(); 246 | } 247 | if self.controller.move_backward { 248 | // If we moving backward then subtract "look" vector of the body. 249 | velocity -= body.look_vector(); 250 | } 251 | if self.controller.move_left { 252 | // If we moving left then add "side" vector of the body. 253 | velocity += body.side_vector(); 254 | } 255 | if self.controller.move_right { 256 | // If we moving right then subtract "side" vector of the body. 257 | velocity -= body.side_vector(); 258 | } 259 | 260 | // Finally new linear velocity. 261 | body.set_lin_vel(velocity); 262 | 263 | // Change the rotation of the rigid body according to current yaw. These lines responsible for 264 | // left-right rotation. 265 | body.local_transform_mut() 266 | .set_rotation(UnitQuaternion::from_axis_angle( 267 | &Vector3::y_axis(), 268 | self.controller.yaw.to_radians(), 269 | )); 270 | 271 | if self.controller.shoot { 272 | self.sender 273 | .send(Message::ShootWeapon { 274 | weapon: self.weapon, 275 | }) 276 | .unwrap(); 277 | } 278 | } 279 | 280 | fn process_input_event(&mut self, event: &Event<()>) { 281 | match event { 282 | Event::WindowEvent { event, .. } => match event { 283 | WindowEvent::KeyboardInput { input, .. } => { 284 | if let Some(key_code) = input.virtual_keycode { 285 | match key_code { 286 | VirtualKeyCode::W => { 287 | self.controller.move_forward = input.state == ElementState::Pressed; 288 | } 289 | VirtualKeyCode::S => { 290 | self.controller.move_backward = 291 | input.state == ElementState::Pressed; 292 | } 293 | VirtualKeyCode::A => { 294 | self.controller.move_left = input.state == ElementState::Pressed; 295 | } 296 | VirtualKeyCode::D => { 297 | self.controller.move_right = input.state == ElementState::Pressed; 298 | } 299 | _ => (), 300 | } 301 | } 302 | } 303 | &WindowEvent::MouseInput { button, state, .. } => { 304 | if button == MouseButton::Left { 305 | self.controller.shoot = state == ElementState::Pressed; 306 | } 307 | } 308 | _ => {} 309 | }, 310 | Event::DeviceEvent { event, .. } => { 311 | if let DeviceEvent::MouseMotion { delta } = event { 312 | let mouse_sens = 0.5; 313 | self.controller.yaw -= mouse_sens * delta.0 as f32; 314 | 315 | self.controller.pitch = 316 | (self.controller.pitch + mouse_sens * delta.1 as f32).clamp(-90.0, 90.0); 317 | } 318 | } 319 | _ => (), 320 | } 321 | } 322 | } 323 | 324 | fn create_shot_trail( 325 | graph: &mut Graph, 326 | origin: Vector3, 327 | direction: Vector3, 328 | trail_length: f32, 329 | ) { 330 | let transform = TransformBuilder::new() 331 | .with_local_position(origin) 332 | // Scale the trail in XZ plane to make it thin, and apply `trail_length` scale on Y axis 333 | // to stretch is out. 334 | .with_local_scale(Vector3::new(0.0025, 0.0025, trail_length)) 335 | // Rotate the trail along given `direction` 336 | .with_local_rotation(UnitQuaternion::face_towards(&direction, &Vector3::y())) 337 | .build(); 338 | 339 | // Create unit cylinder with caps that faces toward Z axis. 340 | let shape = SurfaceSharedData::new(SurfaceData::make_cylinder( 341 | 6, // Count of sides 342 | 1.0, // Radius 343 | 1.0, // Height 344 | false, // No caps are needed. 345 | // Rotate vertical cylinder around X axis to make it face towards Z axis 346 | &UnitQuaternion::from_axis_angle(&Vector3::x_axis(), 90.0f32.to_radians()).to_homogeneous(), 347 | )); 348 | 349 | // Create an instance of standard material for the shot trail. 350 | let mut material = Material::standard(); 351 | material 352 | .set_property( 353 | &ImmutableString::new("diffuseColor"), 354 | // Set yellow-ish color. 355 | PropertyValue::Color(Color::from_rgba(255, 255, 0, 120)), 356 | ) 357 | .unwrap(); 358 | 359 | MeshBuilder::new( 360 | BaseBuilder::new() 361 | // Do not cast shadows. 362 | .with_cast_shadows(false) 363 | .with_local_transform(transform) 364 | // Shot trail should live ~0.25 seconds, after that it will be automatically 365 | // destroyed. 366 | .with_lifetime(0.25), 367 | ) 368 | .with_surfaces(vec![SurfaceBuilder::new(shape) 369 | .with_material(SharedMaterial::new(material)) 370 | .build()]) 371 | // Make sure to set Forward render path, otherwise the object won't be 372 | // transparent. 373 | .with_render_path(RenderPath::Forward) 374 | .build(graph); 375 | } 376 | 377 | struct Game { 378 | scene: Handle, 379 | player: Player, 380 | weapons: Pool, 381 | receiver: Receiver, 382 | sender: Sender, 383 | } 384 | 385 | impl Game { 386 | pub async fn new(engine: &mut Engine) -> Self { 387 | // Make message queue. 388 | let (sender, receiver) = mpsc::channel(); 389 | 390 | let mut scene = Scene::new(); 391 | 392 | // Load a scene resource and create its instance. 393 | engine 394 | .resource_manager 395 | .request_model("data/models/scene.rgs") 396 | .await 397 | .unwrap() 398 | .instantiate(&mut scene); 399 | 400 | // Create player first. 401 | let mut player = 402 | Player::new(&mut scene, engine.resource_manager.clone(), sender.clone()).await; 403 | 404 | // Create weapon next. 405 | let weapon = Weapon::new(&mut scene, engine.resource_manager.clone()).await; 406 | 407 | // "Attach" the weapon to the weapon pivot of the player. 408 | scene.graph.link_nodes(weapon.model(), player.weapon_pivot); 409 | 410 | // Create a container for the weapons. 411 | let mut weapons = Pool::new(); 412 | 413 | // Put the weapon into it - this operation moves the weapon in the pool and returns handle. 414 | let weapon = weapons.spawn(weapon); 415 | 416 | // "Give" the weapon to the player. 417 | player.weapon = weapon; 418 | 419 | Self { 420 | player, 421 | scene: engine.scenes.add(scene), 422 | weapons, 423 | sender, 424 | receiver, 425 | } 426 | } 427 | 428 | fn shoot_weapon(&mut self, weapon: Handle, engine: &mut Engine) { 429 | let weapon = &mut self.weapons[weapon]; 430 | 431 | if weapon.can_shoot() { 432 | weapon.shoot(); 433 | 434 | let scene = &mut engine.scenes[self.scene]; 435 | 436 | let weapon_model = &scene.graph[weapon.model()]; 437 | 438 | // Make a ray that starts at the weapon's position in the world and look toward 439 | // "look" vector of the weapon. 440 | let ray = Ray::new( 441 | scene.graph[weapon.shot_point()].global_position(), 442 | weapon_model.look_vector().scale(1000.0), 443 | ); 444 | 445 | let mut intersections = Vec::new(); 446 | 447 | scene.graph.physics.cast_ray( 448 | RayCastOptions { 449 | ray_origin: Point3::from(ray.origin), 450 | max_len: ray.dir.norm(), 451 | groups: Default::default(), 452 | sort_results: true, // We need intersections to be sorted from closest to furthest. 453 | ray_direction: ray.dir, 454 | }, 455 | &mut intersections, 456 | ); 457 | 458 | // Ignore intersections with player's capsule. 459 | let trail_length = if let Some(intersection) = intersections 460 | .iter() 461 | .find(|i| i.collider != self.player.collider) 462 | { 463 | // 464 | // TODO: Add code to handle intersections with bots. 465 | // 466 | 467 | // For now just apply some force at the point of impact. 468 | let colliders_parent = scene.graph[intersection.collider].parent(); 469 | let picked_rigid_body = scene.graph[colliders_parent].as_rigid_body_mut(); 470 | picked_rigid_body.apply_force_at_point( 471 | ray.dir.normalize().scale(10.0), 472 | intersection.position.coords, 473 | ); 474 | picked_rigid_body.wake_up(); 475 | 476 | // Add bullet impact effect. 477 | let effect_orientation = vector_to_quat(intersection.normal); 478 | 479 | create_bullet_impact( 480 | &mut scene.graph, 481 | engine.resource_manager.clone(), 482 | intersection.position.coords, 483 | effect_orientation, 484 | ); 485 | 486 | // Trail length will be the length of line between intersection point and ray origin. 487 | (intersection.position.coords - ray.origin).norm() 488 | } else { 489 | // Otherwise trail length will be just the ray length. 490 | ray.dir.norm() 491 | }; 492 | 493 | create_shot_trail(&mut scene.graph, ray.origin, ray.dir, trail_length); 494 | } 495 | } 496 | 497 | pub fn update(&mut self, engine: &mut Engine, dt: f32) { 498 | let scene = &mut engine.scenes[self.scene]; 499 | 500 | self.player.update(scene); 501 | 502 | for weapon in self.weapons.iter_mut() { 503 | weapon.update(dt, &mut scene.graph); 504 | } 505 | 506 | // We're using `try_recv` here because we don't want to wait until next message - 507 | // if the queue is empty just continue to next frame. 508 | while let Ok(message) = self.receiver.try_recv() { 509 | match message { 510 | Message::ShootWeapon { weapon } => { 511 | self.shoot_weapon(weapon, engine); 512 | } 513 | } 514 | } 515 | } 516 | } 517 | 518 | fn main() { 519 | // Configure main window first. 520 | let window_builder = WindowBuilder::new().with_title("3D Shooter Tutorial"); 521 | // Create event loop that will be used to "listen" events from the OS. 522 | let event_loop = EventLoop::new(); 523 | 524 | // Finally create an instance of the engine. 525 | let serialization_context = Arc::new(SerializationContext::new()); 526 | let mut engine = Engine::new(EngineInitParams { 527 | window_builder, 528 | resource_manager: ResourceManager::new(serialization_context.clone()), 529 | serialization_context, 530 | events_loop: &event_loop, 531 | vsync: false, 532 | headless: false, 533 | }) 534 | .unwrap(); 535 | 536 | // Initialize game instance. 537 | let mut game = fyrox::core::futures::executor::block_on(Game::new(&mut engine)); 538 | 539 | // Run the event loop of the main window. which will respond to OS and window events and update 540 | // engine's state accordingly. Engine lets you to decide which event should be handled, 541 | // this is minimal working example if how it should be. 542 | let mut previous = time::Instant::now(); 543 | let mut lag = 0.0; 544 | event_loop.run(move |event, _, control_flow| { 545 | game.player.process_input_event(&event); 546 | 547 | match event { 548 | Event::MainEventsCleared => { 549 | // This main game loop - it has fixed time step which means that game 550 | // code will run at fixed speed even if renderer can't give you desired 551 | // 60 fps. 552 | let elapsed = previous.elapsed(); 553 | previous = time::Instant::now(); 554 | lag += elapsed.as_secs_f32(); 555 | while lag >= TIMESTEP { 556 | lag -= TIMESTEP; 557 | 558 | // Run our game's logic. 559 | game.update(&mut engine, TIMESTEP); 560 | 561 | // Update engine each frame. 562 | engine.update(TIMESTEP, control_flow, &mut lag, Default::default()); 563 | } 564 | 565 | // Rendering must be explicitly requested and handled after RedrawRequested event is received. 566 | engine.get_window().request_redraw(); 567 | } 568 | Event::RedrawRequested(_) => { 569 | // Render at max speed - it is not tied to the game code. 570 | engine.render().unwrap(); 571 | } 572 | Event::WindowEvent { event, .. } => match event { 573 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 574 | WindowEvent::KeyboardInput { input, .. } => { 575 | // Exit game by hitting Escape. 576 | if let Some(VirtualKeyCode::Escape) = input.virtual_keycode { 577 | *control_flow = ControlFlow::Exit 578 | } 579 | } 580 | WindowEvent::Resized(size) => { 581 | // It is very important to handle Resized event from window, because 582 | // renderer knows nothing about window size - it must be notified 583 | // directly when window size has changed. 584 | engine.set_frame_size(size.into()).unwrap(); 585 | } 586 | _ => (), 587 | }, 588 | _ => *control_flow = ControlFlow::Poll, 589 | } 590 | }); 591 | } 592 | -------------------------------------------------------------------------------- /tutorial2-weapons/src/message.rs: -------------------------------------------------------------------------------- 1 | use crate::weapon::Weapon; 2 | use fyrox::core::pool::Handle; 3 | 4 | pub enum Message { 5 | ShootWeapon { weapon: Handle }, 6 | } 7 | -------------------------------------------------------------------------------- /tutorial2-weapons/src/weapon.rs: -------------------------------------------------------------------------------- 1 | use fyrox::scene::graph::Graph; 2 | use fyrox::{ 3 | core::{algebra::Vector3, math::Vector3Ext, pool::Handle}, 4 | engine::resource_manager::ResourceManager, 5 | scene::{node::Node, Scene}, 6 | }; 7 | 8 | pub struct Weapon { 9 | model: Handle, 10 | shot_point: Handle, 11 | shot_timer: f32, 12 | recoil_offset: Vector3, 13 | recoil_target_offset: Vector3, 14 | } 15 | 16 | impl Weapon { 17 | pub async fn new(scene: &mut Scene, resource_manager: ResourceManager) -> Self { 18 | // Yeah, you need only few lines of code to load a model of any complexity. 19 | let model = resource_manager 20 | .request_model("data/models/m4.FBX") 21 | .await 22 | .unwrap() 23 | .instantiate(scene); 24 | 25 | let shot_point = scene.graph.find_by_name(model, "Weapon:ShotPoint"); 26 | 27 | Self { 28 | model, 29 | shot_point, 30 | shot_timer: 0.0, 31 | recoil_offset: Default::default(), 32 | recoil_target_offset: Default::default(), 33 | } 34 | } 35 | 36 | pub fn model(&self) -> Handle { 37 | self.model 38 | } 39 | 40 | pub fn shot_point(&self) -> Handle { 41 | self.shot_point 42 | } 43 | 44 | pub fn update(&mut self, dt: f32, graph: &mut Graph) { 45 | self.shot_timer = (self.shot_timer - dt).max(0.0); 46 | 47 | // `follow` method defined in Vector3Ext trait and it just increases or 48 | // decreases vector's value in order to "follow" the target value with 49 | // given speed. 50 | self.recoil_offset.follow(&self.recoil_target_offset, 0.5); 51 | 52 | // Apply offset to weapon's model. 53 | graph[self.model] 54 | .local_transform_mut() 55 | .set_position(self.recoil_offset); 56 | 57 | // Check if we've reached target recoil offset. 58 | if self 59 | .recoil_offset 60 | .metric_distance(&self.recoil_target_offset) 61 | < 0.001 62 | { 63 | // And if so, reset offset to zero to return weapon at 64 | // its default position. 65 | self.recoil_target_offset = Default::default(); 66 | } 67 | } 68 | 69 | pub fn can_shoot(&self) -> bool { 70 | self.shot_timer <= 0.0 71 | } 72 | 73 | pub fn shoot(&mut self) { 74 | self.shot_timer = 0.1; 75 | 76 | self.recoil_target_offset = Vector3::new(0.0, 0.0, -0.025); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tutorial3-bots-ai/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | *.idea 13 | *.log -------------------------------------------------------------------------------- /tutorial3-bots-ai/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tutorial-shooter" 3 | version = "0.2.0" 4 | authors = ["Dmitry Stepanov "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | fyrox = {path = "../../fyrox/", version = "0.29"} -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/animations/zombie_attack.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/animations/zombie_attack.fbx -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/animations/zombie_idle.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/animations/zombie_idle.fbx -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/animations/zombie_walk.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/animations/zombie_walk.fbx -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/Ch10_1001_Diffuse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/Ch10_1001_Diffuse.png -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/Ch10_1001_Glossiness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/Ch10_1001_Glossiness.png -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/Ch10_1001_Normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/Ch10_1001_Normal.png -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/Ch10_1001_Specular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/Ch10_1001_Specular.png -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/Ch10_1002_Diffuse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/Ch10_1002_Diffuse.png -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/Ch10_1002_Glossiness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/Ch10_1002_Glossiness.png -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/Ch10_1002_Normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/Ch10_1002_Normal.png -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/M4_Body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/M4_Body.png -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/M4_Body_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/M4_Body_normal.png -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/M4_Magazine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/M4_Magazine.png -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/M4_Magazine_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/M4_Magazine_normal.png -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/M4_Sight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/M4_Sight.png -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/M4_Sight_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/M4_Sight_normal.png -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/barrel.FBX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/barrel.FBX -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/barrel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/barrel.jpg -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/barrel_normal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/barrel_normal.jpg -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/m4.FBX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/m4.FBX -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/scene.rgs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/scene.rgs -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/models/zombie.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/models/zombie.fbx -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/textures/floor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/textures/floor.jpg -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/textures/skybox/back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/textures/skybox/back.jpg -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/textures/skybox/down.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/textures/skybox/down.jpg -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/textures/skybox/front.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/textures/skybox/front.jpg -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/textures/skybox/left.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/textures/skybox/left.jpg -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/textures/skybox/right.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/textures/skybox/right.jpg -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/textures/skybox/up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/textures/skybox/up.jpg -------------------------------------------------------------------------------- /tutorial3-bots-ai/data/textures/spark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyroxEngine/Fyrox-tutorials/b0f18c117e79ade4645e2892597f40b2a76fe668/tutorial3-bots-ai/data/textures/spark.png -------------------------------------------------------------------------------- /tutorial3-bots-ai/src/bot.rs: -------------------------------------------------------------------------------- 1 | use fyrox::animation::machine::MachineLayer; 2 | use fyrox::scene::animation::{AnimationPlayer, AnimationPlayerBuilder}; 3 | use fyrox::{ 4 | animation::{ 5 | machine::{Machine, Parameter, PoseNode, State, Transition}, 6 | Animation, 7 | }, 8 | core::{ 9 | algebra::{UnitQuaternion, Vector3}, 10 | pool::Handle, 11 | }, 12 | engine::resource_manager::ResourceManager, 13 | resource::model::Model, 14 | scene::{ 15 | base::BaseBuilder, 16 | collider::{ColliderBuilder, ColliderShape}, 17 | node::Node, 18 | rigidbody::RigidBodyBuilder, 19 | transform::TransformBuilder, 20 | Scene, 21 | }, 22 | }; 23 | 24 | pub struct Bot { 25 | rigid_body: Handle, 26 | collider: Handle, 27 | machine: BotAnimationMachine, 28 | follow_target: bool, 29 | } 30 | 31 | impl Bot { 32 | pub async fn new( 33 | scene: &mut Scene, 34 | position: Vector3, 35 | resource_manager: ResourceManager, 36 | ) -> Self { 37 | // Load bot 3D model as usual. 38 | let model = resource_manager 39 | .request_model("data/models/zombie.fbx") 40 | .await 41 | .unwrap() 42 | .instantiate(scene); 43 | 44 | scene.graph[model] 45 | .local_transform_mut() 46 | // Move the model a bit down to make sure bot's feet will be on ground. 47 | .set_position(Vector3::new(0.0, -0.45, 0.0)) 48 | // Scale the model because it is too big. 49 | .set_scale(Vector3::new(0.0047, 0.0047, 0.0047)); 50 | 51 | let collider; 52 | let rigid_body = RigidBodyBuilder::new( 53 | BaseBuilder::new() 54 | .with_local_transform( 55 | TransformBuilder::new() 56 | .with_local_position(Vector3::new(position.x, position.y, position.z)) 57 | .build(), 58 | ) 59 | .with_children(&[ 60 | // Attach model to the rigid body. 61 | model, 62 | // Add capsule collider for the rigid body. 63 | { 64 | collider = ColliderBuilder::new(BaseBuilder::new()) 65 | .with_shape(ColliderShape::capsule_y(0.25, 0.2)) 66 | .build(&mut scene.graph); 67 | collider 68 | }, 69 | ]), 70 | ) 71 | // We don't want a bot to tilt. 72 | .with_locked_rotations(true) 73 | .with_can_sleep(false) 74 | .build(&mut scene.graph); 75 | 76 | Self { 77 | machine: BotAnimationMachine::new(scene, model, resource_manager).await, 78 | rigid_body, 79 | collider, 80 | follow_target: false, 81 | } 82 | } 83 | 84 | pub fn update(&mut self, scene: &mut Scene, dt: f32, target: Vector3) { 85 | let attack_distance = 0.6; 86 | 87 | // Simple AI - follow target by a straight line. 88 | let self_position = scene.graph[self.rigid_body].global_position(); 89 | let direction = target - self_position; 90 | 91 | // Distance to target. 92 | let distance = direction.norm(); 93 | 94 | if distance != 0.0 && distance < 1.5 { 95 | self.follow_target = true; 96 | } 97 | 98 | if self.follow_target && distance != 0.0 { 99 | let rigid_body = scene.graph[self.rigid_body].as_rigid_body_mut(); 100 | 101 | // Make sure bot is facing towards the target. 102 | rigid_body 103 | .local_transform_mut() 104 | .set_rotation(UnitQuaternion::face_towards( 105 | &Vector3::new(direction.x, 0.0, direction.z), 106 | &Vector3::y_axis(), 107 | )); 108 | 109 | // Move only if we're far enough from the target. 110 | if distance > attack_distance { 111 | // Normalize direction vector and scale it by movement speed. 112 | let xz_velocity = direction.scale(1.0 / distance).scale(0.9); 113 | 114 | let new_velocity = 115 | Vector3::new(xz_velocity.x, rigid_body.lin_vel().y, xz_velocity.z); 116 | 117 | rigid_body.set_lin_vel(new_velocity); 118 | } 119 | } 120 | 121 | // For now these are set to false which will force bot to be in idle state. 122 | let input = BotAnimationMachineInput { 123 | walk: self.follow_target && distance > attack_distance, 124 | attack: distance < attack_distance, 125 | }; 126 | 127 | self.machine.update(scene, dt, input); 128 | } 129 | } 130 | 131 | // Simple helper method to create a state supplied with PlayAnimation node. 132 | fn create_play_animation_state( 133 | animation_resource: Model, 134 | name: &str, 135 | layer: &mut MachineLayer, 136 | scene: &mut Scene, 137 | model: Handle, 138 | ) -> (Handle, Handle) { 139 | // Animations retargetting just makes an instance of animation and binds it to 140 | // given model using names of bones. 141 | let animation = *animation_resource 142 | .retarget_animations(model, &mut scene.graph) 143 | .get(0) 144 | .unwrap(); 145 | // Create new PlayAnimation node and add it to machine. 146 | let node = layer.add_node(PoseNode::make_play_animation(animation)); 147 | // Make a state using the node we've made. 148 | let state = layer.add_state(State::new(name, node)); 149 | (animation, state) 150 | } 151 | 152 | pub struct BotAnimationMachineInput { 153 | // Whether a bot is walking or not. 154 | pub walk: bool, 155 | // Whether a bot is attacking or not. 156 | pub attack: bool, 157 | } 158 | 159 | pub struct BotAnimationMachine { 160 | animation_player: Handle, 161 | machine: Machine, 162 | } 163 | 164 | impl BotAnimationMachine { 165 | // Names of parameters that will be used for transition rules in machine. 166 | const IDLE_TO_WALK: &'static str = "IdleToWalk"; 167 | const WALK_TO_IDLE: &'static str = "WalkToIdle"; 168 | const WALK_TO_ATTACK: &'static str = "WalkToAttack"; 169 | const IDLE_TO_ATTACK: &'static str = "IdleToAttack"; 170 | const ATTACK_TO_IDLE: &'static str = "AttackToIdle"; 171 | const ATTACK_TO_WALK: &'static str = "AttackToWalk"; 172 | 173 | pub async fn new( 174 | scene: &mut Scene, 175 | model: Handle, 176 | resource_manager: ResourceManager, 177 | ) -> Self { 178 | let animation_player = 179 | AnimationPlayerBuilder::new(BaseBuilder::new()).build(&mut scene.graph); 180 | scene.graph.link_nodes(animation_player, model); 181 | 182 | let mut machine = Machine::new(); 183 | 184 | let root = machine.layers_mut().first_mut().unwrap(); 185 | 186 | // Load animations in parallel. 187 | let (walk_animation_resource, idle_animation_resource, attack_animation_resource) = fyrox::core::futures::join!( 188 | resource_manager.request_model("data/animations/zombie_walk.fbx"), 189 | resource_manager.request_model("data/animations/zombie_idle.fbx"), 190 | resource_manager.request_model("data/animations/zombie_attack.fbx"), 191 | ); 192 | 193 | // Now create three states with different animations. 194 | let (_, idle_state) = create_play_animation_state( 195 | idle_animation_resource.unwrap(), 196 | "Idle", 197 | root, 198 | scene, 199 | model, 200 | ); 201 | 202 | let (walk_animation, walk_state) = create_play_animation_state( 203 | walk_animation_resource.unwrap(), 204 | "Walk", 205 | root, 206 | scene, 207 | model, 208 | ); 209 | 210 | let (attack_animation, attack_state) = create_play_animation_state( 211 | attack_animation_resource.unwrap(), 212 | "Attack", 213 | root, 214 | scene, 215 | model, 216 | ); 217 | 218 | // Next, define transitions between states. 219 | root.add_transition(Transition::new( 220 | // A name for debugging. 221 | "Idle->Walk", 222 | // Source state. 223 | idle_state, 224 | // Target state. 225 | walk_state, 226 | // Transition time in seconds. 227 | 0.4, 228 | // A name of transition rule parameter. 229 | Self::IDLE_TO_WALK, 230 | )); 231 | root.add_transition(Transition::new( 232 | "Walk->Idle", 233 | walk_state, 234 | idle_state, 235 | 0.4, 236 | Self::WALK_TO_IDLE, 237 | )); 238 | root.add_transition(Transition::new( 239 | "Walk->Attack", 240 | walk_state, 241 | attack_state, 242 | 0.4, 243 | Self::WALK_TO_ATTACK, 244 | )); 245 | root.add_transition(Transition::new( 246 | "Idle->Attack", 247 | idle_state, 248 | attack_state, 249 | 0.4, 250 | Self::IDLE_TO_ATTACK, 251 | )); 252 | root.add_transition(Transition::new( 253 | "Attack->Idle", 254 | attack_state, 255 | idle_state, 256 | 0.4, 257 | Self::ATTACK_TO_IDLE, 258 | )); 259 | root.add_transition(Transition::new( 260 | "Attack->Walk", 261 | attack_state, 262 | walk_state, 263 | 0.4, 264 | Self::ATTACK_TO_WALK, 265 | )); 266 | 267 | // Define entry state. 268 | root.set_entry_state(idle_state); 269 | 270 | Self { 271 | animation_player, 272 | machine, 273 | } 274 | } 275 | 276 | pub fn update(&mut self, scene: &mut Scene, dt: f32, input: BotAnimationMachineInput) { 277 | let animation_player = scene.graph[self.animation_player] 278 | .query_component_ref::() 279 | .unwrap(); 280 | 281 | self.machine 282 | // Set transition parameters. 283 | .set_parameter(Self::WALK_TO_IDLE, Parameter::Rule(!input.walk)) 284 | .set_parameter(Self::IDLE_TO_WALK, Parameter::Rule(input.walk)) 285 | .set_parameter(Self::WALK_TO_ATTACK, Parameter::Rule(input.attack)) 286 | .set_parameter(Self::IDLE_TO_ATTACK, Parameter::Rule(input.attack)) 287 | .set_parameter(Self::ATTACK_TO_IDLE, Parameter::Rule(!input.attack)) 288 | .set_parameter(Self::ATTACK_TO_WALK, Parameter::Rule(!input.attack)) 289 | // Update machine and evaluate final pose. 290 | .evaluate_pose(animation_player.animations(), dt) 291 | // Apply the pose to the graph. 292 | .apply(&mut scene.graph); 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /tutorial3-bots-ai/src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::{bot::Bot, message::Message, weapon::Weapon}; 2 | use fyrox::{ 3 | core::{ 4 | algebra::{Point3, UnitQuaternion, Vector3}, 5 | color::Color, 6 | color_gradient::{ColorGradient, GradientPoint}, 7 | math::{ray::Ray, vector_to_quat}, 8 | pool::{Handle, Pool}, 9 | sstorage::ImmutableString, 10 | }, 11 | engine::{resource_manager::ResourceManager, Engine, EngineInitParams, SerializationContext}, 12 | event::{DeviceEvent, ElementState, Event, MouseButton, VirtualKeyCode, WindowEvent}, 13 | event_loop::{ControlFlow, EventLoop}, 14 | material::{Material, PropertyValue, SharedMaterial}, 15 | resource::texture::TextureWrapMode, 16 | scene::{ 17 | base::BaseBuilder, 18 | camera::{CameraBuilder, SkyBox, SkyBoxBuilder}, 19 | collider::{ColliderBuilder, ColliderShape}, 20 | graph::{physics::RayCastOptions, Graph}, 21 | mesh::{ 22 | surface::{SurfaceBuilder, SurfaceData, SurfaceSharedData}, 23 | MeshBuilder, RenderPath, 24 | }, 25 | node::Node, 26 | particle_system::{ 27 | emitter::base::BaseEmitterBuilder, emitter::sphere::SphereEmitterBuilder, 28 | ParticleSystemBuilder, 29 | }, 30 | pivot::PivotBuilder, 31 | rigidbody::RigidBodyBuilder, 32 | transform::TransformBuilder, 33 | Scene, 34 | }, 35 | window::WindowBuilder, 36 | }; 37 | use std::{ 38 | path::Path, 39 | sync::{ 40 | mpsc::{self, Receiver, Sender}, 41 | Arc, 42 | }, 43 | time, 44 | }; 45 | 46 | pub mod bot; 47 | pub mod message; 48 | pub mod weapon; 49 | 50 | // Our game logic will be updated at 60 Hz rate. 51 | const TIMESTEP: f32 = 1.0 / 60.0; 52 | 53 | #[derive(Default)] 54 | struct InputController { 55 | move_forward: bool, 56 | move_backward: bool, 57 | move_left: bool, 58 | move_right: bool, 59 | pitch: f32, 60 | yaw: f32, 61 | shoot: bool, 62 | } 63 | 64 | struct Player { 65 | camera: Handle, 66 | rigid_body: Handle, 67 | controller: InputController, 68 | weapon_pivot: Handle, 69 | sender: Sender, 70 | weapon: Handle, 71 | collider: Handle, 72 | } 73 | 74 | async fn create_skybox(resource_manager: ResourceManager) -> SkyBox { 75 | // Load skybox textures in parallel. 76 | let (front, back, left, right, top, bottom) = fyrox::core::futures::join!( 77 | resource_manager.request_texture("data/textures/skybox/front.jpg"), 78 | resource_manager.request_texture("data/textures/skybox/back.jpg"), 79 | resource_manager.request_texture("data/textures/skybox/left.jpg"), 80 | resource_manager.request_texture("data/textures/skybox/right.jpg"), 81 | resource_manager.request_texture("data/textures/skybox/up.jpg"), 82 | resource_manager.request_texture("data/textures/skybox/down.jpg") 83 | ); 84 | 85 | // Unwrap everything. 86 | let skybox = SkyBoxBuilder { 87 | front: Some(front.unwrap()), 88 | back: Some(back.unwrap()), 89 | left: Some(left.unwrap()), 90 | right: Some(right.unwrap()), 91 | top: Some(top.unwrap()), 92 | bottom: Some(bottom.unwrap()), 93 | } 94 | .build() 95 | .unwrap(); 96 | 97 | // Set S and T coordinate wrap mode, ClampToEdge will remove any possible seams on edges 98 | // of the skybox. 99 | let skybox_texture = skybox.cubemap().unwrap(); 100 | let mut data = skybox_texture.data_ref(); 101 | data.set_s_wrap_mode(TextureWrapMode::ClampToEdge); 102 | data.set_t_wrap_mode(TextureWrapMode::ClampToEdge); 103 | 104 | skybox 105 | } 106 | 107 | fn create_bullet_impact( 108 | graph: &mut Graph, 109 | resource_manager: ResourceManager, 110 | pos: Vector3, 111 | orientation: UnitQuaternion, 112 | ) -> Handle { 113 | // Create sphere emitter first. 114 | let emitter = SphereEmitterBuilder::new( 115 | BaseEmitterBuilder::new() 116 | .with_max_particles(200) 117 | .with_spawn_rate(3000) 118 | .with_size_modifier_range(-0.01..-0.0125) 119 | .with_size_range(0.0075..0.015) 120 | .with_lifetime_range(0.05..0.2) 121 | .with_x_velocity_range(-0.0075..0.0075) 122 | .with_y_velocity_range(-0.0075..0.0075) 123 | .with_z_velocity_range(0.025..0.045) 124 | .resurrect_particles(false), 125 | ) 126 | .with_radius(0.01) 127 | .build(); 128 | 129 | // Color gradient will be used to modify color of each particle over its lifetime. 130 | let color_gradient = { 131 | let mut gradient = ColorGradient::new(); 132 | gradient.add_point(GradientPoint::new(0.00, Color::from_rgba(255, 255, 0, 0))); 133 | gradient.add_point(GradientPoint::new(0.05, Color::from_rgba(255, 160, 0, 255))); 134 | gradient.add_point(GradientPoint::new(0.95, Color::from_rgba(255, 120, 0, 255))); 135 | gradient.add_point(GradientPoint::new(1.00, Color::from_rgba(255, 60, 0, 0))); 136 | gradient 137 | }; 138 | 139 | // Create new transform to orient and position particle system. 140 | let transform = TransformBuilder::new() 141 | .with_local_position(pos) 142 | .with_local_rotation(orientation) 143 | .build(); 144 | 145 | // Finally create particle system with limited lifetime. 146 | ParticleSystemBuilder::new( 147 | BaseBuilder::new() 148 | .with_lifetime(1.0) 149 | .with_local_transform(transform), 150 | ) 151 | .with_acceleration(Vector3::new(0.0, 0.0, 0.0)) 152 | .with_color_over_lifetime_gradient(color_gradient) 153 | .with_emitters(vec![emitter]) 154 | // We'll use simple spark texture for each particle. 155 | .with_texture(resource_manager.request_texture(Path::new("data/textures/spark.png"))) 156 | .build(graph) 157 | } 158 | 159 | impl Player { 160 | async fn new( 161 | scene: &mut Scene, 162 | resource_manager: ResourceManager, 163 | sender: Sender, 164 | ) -> Self { 165 | // Create rigid body with a camera, move it a bit up to "emulate" head. 166 | let camera; 167 | let weapon_pivot; 168 | let collider; 169 | let rigid_body_handle = RigidBodyBuilder::new( 170 | BaseBuilder::new() 171 | .with_local_transform( 172 | TransformBuilder::new() 173 | // Offset player a bit. 174 | .with_local_position(Vector3::new(0.0, 1.0, -1.0)) 175 | .build(), 176 | ) 177 | .with_children(&[ 178 | { 179 | camera = CameraBuilder::new( 180 | BaseBuilder::new() 181 | .with_local_transform( 182 | TransformBuilder::new() 183 | .with_local_position(Vector3::new(0.0, 0.25, 0.0)) 184 | .build(), 185 | ) 186 | .with_children(&[{ 187 | weapon_pivot = PivotBuilder::new( 188 | BaseBuilder::new().with_local_transform( 189 | TransformBuilder::new() 190 | .with_local_position(Vector3::new( 191 | -0.1, -0.05, 0.015, 192 | )) 193 | .build(), 194 | ), 195 | ) 196 | .build(&mut scene.graph); 197 | weapon_pivot 198 | }]), 199 | ) 200 | .with_skybox(create_skybox(resource_manager).await) 201 | .build(&mut scene.graph); 202 | camera 203 | }, 204 | // Add capsule collider for the rigid body. 205 | { 206 | collider = ColliderBuilder::new(BaseBuilder::new()) 207 | .with_shape(ColliderShape::capsule_y(0.25, 0.2)) 208 | .build(&mut scene.graph); 209 | collider 210 | }, 211 | ]), 212 | ) 213 | // We don't want the player to tilt. 214 | .with_locked_rotations(true) 215 | // We don't want the rigid body to sleep (be excluded from simulation) 216 | .with_can_sleep(false) 217 | .build(&mut scene.graph); 218 | 219 | Self { 220 | camera, 221 | weapon_pivot, 222 | rigid_body: rigid_body_handle, 223 | controller: Default::default(), 224 | sender, 225 | collider, 226 | weapon: Default::default(), // Leave it unassigned for now. 227 | } 228 | } 229 | 230 | fn update(&mut self, scene: &mut Scene) { 231 | // Set pitch for the camera. These lines responsible for up-down camera rotation. 232 | scene.graph[self.camera].local_transform_mut().set_rotation( 233 | UnitQuaternion::from_axis_angle(&Vector3::x_axis(), self.controller.pitch.to_radians()), 234 | ); 235 | // Borrow rigid body node. 236 | let body = scene.graph[self.rigid_body].as_rigid_body_mut(); 237 | 238 | // Keep only vertical velocity, and drop horizontal. 239 | let mut velocity = Vector3::new(0.0, body.lin_vel().y, 0.0); 240 | 241 | // Change the velocity depending on the keys pressed. 242 | if self.controller.move_forward { 243 | // If we moving forward then add "look" vector of the body. 244 | velocity += body.look_vector(); 245 | } 246 | if self.controller.move_backward { 247 | // If we moving backward then subtract "look" vector of the body. 248 | velocity -= body.look_vector(); 249 | } 250 | if self.controller.move_left { 251 | // If we moving left then add "side" vector of the body. 252 | velocity += body.side_vector(); 253 | } 254 | if self.controller.move_right { 255 | // If we moving right then subtract "side" vector of the body. 256 | velocity -= body.side_vector(); 257 | } 258 | 259 | // Finally new linear velocity. 260 | body.set_lin_vel(velocity); 261 | 262 | // Change the rotation of the rigid body according to current yaw. These lines responsible for 263 | // left-right rotation. 264 | body.local_transform_mut() 265 | .set_rotation(UnitQuaternion::from_axis_angle( 266 | &Vector3::y_axis(), 267 | self.controller.yaw.to_radians(), 268 | )); 269 | 270 | if self.controller.shoot { 271 | self.sender 272 | .send(Message::ShootWeapon { 273 | weapon: self.weapon, 274 | }) 275 | .unwrap(); 276 | } 277 | } 278 | 279 | fn process_input_event(&mut self, event: &Event<()>) { 280 | match event { 281 | Event::WindowEvent { event, .. } => match event { 282 | WindowEvent::KeyboardInput { input, .. } => { 283 | if let Some(key_code) = input.virtual_keycode { 284 | match key_code { 285 | VirtualKeyCode::W => { 286 | self.controller.move_forward = input.state == ElementState::Pressed; 287 | } 288 | VirtualKeyCode::S => { 289 | self.controller.move_backward = 290 | input.state == ElementState::Pressed; 291 | } 292 | VirtualKeyCode::A => { 293 | self.controller.move_left = input.state == ElementState::Pressed; 294 | } 295 | VirtualKeyCode::D => { 296 | self.controller.move_right = input.state == ElementState::Pressed; 297 | } 298 | _ => (), 299 | } 300 | } 301 | } 302 | &WindowEvent::MouseInput { button, state, .. } => { 303 | if button == MouseButton::Left { 304 | self.controller.shoot = state == ElementState::Pressed; 305 | } 306 | } 307 | _ => {} 308 | }, 309 | Event::DeviceEvent { event, .. } => { 310 | if let DeviceEvent::MouseMotion { delta } = event { 311 | let mouse_sens = 0.5; 312 | self.controller.yaw -= mouse_sens * delta.0 as f32; 313 | 314 | self.controller.pitch = 315 | (self.controller.pitch + mouse_sens * delta.1 as f32).clamp(-90.0, 90.0); 316 | } 317 | } 318 | _ => (), 319 | } 320 | } 321 | } 322 | 323 | fn create_shot_trail( 324 | graph: &mut Graph, 325 | origin: Vector3, 326 | direction: Vector3, 327 | trail_length: f32, 328 | ) { 329 | let transform = TransformBuilder::new() 330 | .with_local_position(origin) 331 | // Scale the trail in XZ plane to make it thin, and apply `trail_length` scale on Y axis 332 | // to stretch is out. 333 | .with_local_scale(Vector3::new(0.0025, 0.0025, trail_length)) 334 | // Rotate the trail along given `direction` 335 | .with_local_rotation(UnitQuaternion::face_towards(&direction, &Vector3::y())) 336 | .build(); 337 | 338 | // Create unit cylinder with caps that faces toward Z axis. 339 | let shape = SurfaceSharedData::new(SurfaceData::make_cylinder( 340 | 6, // Count of sides 341 | 1.0, // Radius 342 | 1.0, // Height 343 | false, // No caps are needed. 344 | // Rotate vertical cylinder around X axis to make it face towards Z axis 345 | &UnitQuaternion::from_axis_angle(&Vector3::x_axis(), 90.0f32.to_radians()).to_homogeneous(), 346 | )); 347 | 348 | // Create an instance of standard material for the shot trail. 349 | let mut material = Material::standard(); 350 | material 351 | .set_property( 352 | &ImmutableString::new("diffuseColor"), 353 | // Set yellow-ish color. 354 | PropertyValue::Color(Color::from_rgba(255, 255, 0, 120)), 355 | ) 356 | .unwrap(); 357 | 358 | MeshBuilder::new( 359 | BaseBuilder::new() 360 | // Do not cast shadows. 361 | .with_cast_shadows(false) 362 | .with_local_transform(transform) 363 | // Shot trail should live ~0.25 seconds, after that it will be automatically 364 | // destroyed. 365 | .with_lifetime(0.25), 366 | ) 367 | .with_surfaces(vec![SurfaceBuilder::new(shape) 368 | .with_material(SharedMaterial::new(material)) 369 | .build()]) 370 | // Make sure to set Forward render path, otherwise the object won't be 371 | // transparent. 372 | .with_render_path(RenderPath::Forward) 373 | .build(graph); 374 | } 375 | 376 | struct Game { 377 | scene: Handle, 378 | player: Player, 379 | weapons: Pool, 380 | receiver: Receiver, 381 | sender: Sender, 382 | bots: Pool, 383 | } 384 | 385 | impl Game { 386 | pub async fn new(engine: &mut Engine) -> Self { 387 | // Make message queue. 388 | let (sender, receiver) = mpsc::channel(); 389 | 390 | let mut scene = Scene::new(); 391 | 392 | // Load a scene resource and create its instance. 393 | engine 394 | .resource_manager 395 | .request_model("data/models/scene.rgs") 396 | .await 397 | .unwrap() 398 | .instantiate(&mut scene); 399 | 400 | // Create player first. 401 | let mut player = 402 | Player::new(&mut scene, engine.resource_manager.clone(), sender.clone()).await; 403 | 404 | // Create weapon next. 405 | let weapon = Weapon::new(&mut scene, engine.resource_manager.clone()).await; 406 | 407 | // "Attach" the weapon to the weapon pivot of the player. 408 | scene.graph.link_nodes(weapon.model(), player.weapon_pivot); 409 | 410 | // Create a container for the weapons. 411 | let mut weapons = Pool::new(); 412 | 413 | // Put the weapon into it - this operation moves the weapon in the pool and returns handle. 414 | let weapon = weapons.spawn(weapon); 415 | 416 | // "Give" the weapon to the player. 417 | player.weapon = weapon; 418 | 419 | // Add some bots. 420 | let mut bots = Pool::new(); 421 | 422 | bots.spawn( 423 | Bot::new( 424 | &mut scene, 425 | Vector3::new(-1.0, 1.0, 1.5), 426 | engine.resource_manager.clone(), 427 | ) 428 | .await, 429 | ); 430 | 431 | Self { 432 | player, 433 | scene: engine.scenes.add(scene), 434 | weapons, 435 | sender, 436 | receiver, 437 | bots, 438 | } 439 | } 440 | 441 | fn shoot_weapon(&mut self, weapon: Handle, engine: &mut Engine) { 442 | let weapon = &mut self.weapons[weapon]; 443 | 444 | if weapon.can_shoot() { 445 | weapon.shoot(); 446 | 447 | let scene = &mut engine.scenes[self.scene]; 448 | 449 | let weapon_model = &scene.graph[weapon.model()]; 450 | 451 | // Make a ray that starts at the weapon's position in the world and look toward 452 | // "look" vector of the weapon. 453 | let ray = Ray::new( 454 | scene.graph[weapon.shot_point()].global_position(), 455 | weapon_model.look_vector().scale(1000.0), 456 | ); 457 | 458 | let mut intersections = Vec::new(); 459 | 460 | scene.graph.physics.cast_ray( 461 | RayCastOptions { 462 | ray_origin: Point3::from(ray.origin), 463 | max_len: ray.dir.norm(), 464 | groups: Default::default(), 465 | sort_results: true, // We need intersections to be sorted from closest to furthest. 466 | ray_direction: ray.dir, 467 | }, 468 | &mut intersections, 469 | ); 470 | 471 | // Ignore intersections with player's capsule. 472 | let trail_length = if let Some(intersection) = intersections 473 | .iter() 474 | .find(|i| i.collider != self.player.collider) 475 | { 476 | // 477 | // TODO: Add code to handle intersections with bots. 478 | // 479 | 480 | // For now just apply some force at the point of impact. 481 | let colliders_parent = scene.graph[intersection.collider].parent(); 482 | let picked_rigid_body = scene.graph[colliders_parent].as_rigid_body_mut(); 483 | picked_rigid_body.apply_force_at_point( 484 | ray.dir.normalize().scale(10.0), 485 | intersection.position.coords, 486 | ); 487 | picked_rigid_body.wake_up(); 488 | 489 | // Add bullet impact effect. 490 | let effect_orientation = vector_to_quat(intersection.normal); 491 | 492 | create_bullet_impact( 493 | &mut scene.graph, 494 | engine.resource_manager.clone(), 495 | intersection.position.coords, 496 | effect_orientation, 497 | ); 498 | 499 | // Trail length will be the length of line between intersection point and ray origin. 500 | (intersection.position.coords - ray.origin).norm() 501 | } else { 502 | // Otherwise trail length will be just the ray length. 503 | ray.dir.norm() 504 | }; 505 | 506 | create_shot_trail(&mut scene.graph, ray.origin, ray.dir, trail_length); 507 | } 508 | } 509 | 510 | pub fn update(&mut self, engine: &mut Engine, dt: f32) { 511 | let scene = &mut engine.scenes[self.scene]; 512 | 513 | self.player.update(scene); 514 | 515 | for weapon in self.weapons.iter_mut() { 516 | weapon.update(dt, &mut scene.graph); 517 | } 518 | 519 | let target = scene.graph[self.player.rigid_body].global_position(); 520 | 521 | for bot in self.bots.iter_mut() { 522 | bot.update(scene, dt, target); 523 | } 524 | 525 | // We're using `try_recv` here because we don't want to wait until next message - 526 | // if the queue is empty just continue to next frame. 527 | while let Ok(message) = self.receiver.try_recv() { 528 | match message { 529 | Message::ShootWeapon { weapon } => { 530 | self.shoot_weapon(weapon, engine); 531 | } 532 | } 533 | } 534 | } 535 | } 536 | 537 | fn main() { 538 | // Configure main window first. 539 | let window_builder = WindowBuilder::new().with_title("3D Shooter Tutorial"); 540 | // Create event loop that will be used to "listen" events from the OS. 541 | let event_loop = EventLoop::new(); 542 | 543 | // Finally create an instance of the engine. 544 | let serialization_context = Arc::new(SerializationContext::new()); 545 | let mut engine = Engine::new(EngineInitParams { 546 | window_builder, 547 | resource_manager: ResourceManager::new(serialization_context.clone()), 548 | serialization_context, 549 | events_loop: &event_loop, 550 | vsync: false, 551 | headless: false, 552 | }) 553 | .unwrap(); 554 | 555 | // Initialize game instance. 556 | let mut game = fyrox::core::futures::executor::block_on(Game::new(&mut engine)); 557 | 558 | // Run the event loop of the main window. which will respond to OS and window events and update 559 | // engine's state accordingly. Engine lets you to decide which event should be handled, 560 | // this is minimal working example if how it should be. 561 | let mut previous = time::Instant::now(); 562 | let mut lag = 0.0; 563 | event_loop.run(move |event, _, control_flow| { 564 | game.player.process_input_event(&event); 565 | 566 | match event { 567 | Event::MainEventsCleared => { 568 | // This main game loop - it has fixed time step which means that game 569 | // code will run at fixed speed even if renderer can't give you desired 570 | // 60 fps. 571 | let elapsed = previous.elapsed(); 572 | previous = time::Instant::now(); 573 | lag += elapsed.as_secs_f32(); 574 | while lag >= TIMESTEP { 575 | lag -= TIMESTEP; 576 | 577 | // Run our game's logic. 578 | game.update(&mut engine, TIMESTEP); 579 | 580 | // Update engine each frame. 581 | engine.update(TIMESTEP, control_flow, &mut lag, Default::default()); 582 | } 583 | 584 | // Rendering must be explicitly requested and handled after RedrawRequested event is received. 585 | engine.get_window().request_redraw(); 586 | } 587 | Event::RedrawRequested(_) => { 588 | // Render at max speed - it is not tied to the game code. 589 | engine.render().unwrap(); 590 | } 591 | Event::WindowEvent { event, .. } => match event { 592 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 593 | WindowEvent::KeyboardInput { input, .. } => { 594 | // Exit game by hitting Escape. 595 | if let Some(VirtualKeyCode::Escape) = input.virtual_keycode { 596 | *control_flow = ControlFlow::Exit 597 | } 598 | } 599 | WindowEvent::Resized(size) => { 600 | // It is very important to handle Resized event from window, because 601 | // renderer knows nothing about window size - it must be notified 602 | // directly when window size has changed. 603 | engine.set_frame_size(size.into()).unwrap(); 604 | } 605 | _ => (), 606 | }, 607 | _ => *control_flow = ControlFlow::Poll, 608 | } 609 | }); 610 | } 611 | -------------------------------------------------------------------------------- /tutorial3-bots-ai/src/message.rs: -------------------------------------------------------------------------------- 1 | use crate::weapon::Weapon; 2 | use fyrox::core::pool::Handle; 3 | 4 | pub enum Message { 5 | ShootWeapon { weapon: Handle }, 6 | } 7 | -------------------------------------------------------------------------------- /tutorial3-bots-ai/src/weapon.rs: -------------------------------------------------------------------------------- 1 | use fyrox::{ 2 | core::{algebra::Vector3, math::Vector3Ext, pool::Handle}, 3 | engine::resource_manager::ResourceManager, 4 | scene::{graph::Graph, node::Node, Scene}, 5 | }; 6 | 7 | pub struct Weapon { 8 | model: Handle, 9 | shot_point: Handle, 10 | shot_timer: f32, 11 | recoil_offset: Vector3, 12 | recoil_target_offset: Vector3, 13 | } 14 | 15 | impl Weapon { 16 | pub async fn new(scene: &mut Scene, resource_manager: ResourceManager) -> Self { 17 | // Yeah, you need only few lines of code to load a model of any complexity. 18 | let model = resource_manager 19 | .request_model("data/models/m4.FBX") 20 | .await 21 | .unwrap() 22 | .instantiate(scene); 23 | 24 | let shot_point = scene.graph.find_by_name(model, "Weapon:ShotPoint"); 25 | 26 | Self { 27 | model, 28 | shot_point, 29 | shot_timer: 0.0, 30 | recoil_offset: Default::default(), 31 | recoil_target_offset: Default::default(), 32 | } 33 | } 34 | 35 | pub fn model(&self) -> Handle { 36 | self.model 37 | } 38 | 39 | pub fn shot_point(&self) -> Handle { 40 | self.shot_point 41 | } 42 | 43 | pub fn update(&mut self, dt: f32, graph: &mut Graph) { 44 | self.shot_timer = (self.shot_timer - dt).max(0.0); 45 | 46 | // `follow` method defined in Vector3Ext trait and it just increases or 47 | // decreases vector's value in order to "follow" the target value with 48 | // given speed. 49 | self.recoil_offset.follow(&self.recoil_target_offset, 0.5); 50 | 51 | // Apply offset to weapon's model. 52 | graph[self.model] 53 | .local_transform_mut() 54 | .set_position(self.recoil_offset); 55 | 56 | // Check if we've reached target recoil offset. 57 | if self 58 | .recoil_offset 59 | .metric_distance(&self.recoil_target_offset) 60 | < 0.001 61 | { 62 | // And if so, reset offset to zero to return weapon at 63 | // its default position. 64 | self.recoil_target_offset = Default::default(); 65 | } 66 | } 67 | 68 | pub fn can_shoot(&self) -> bool { 69 | self.shot_timer <= 0.0 70 | } 71 | 72 | pub fn shoot(&mut self) { 73 | self.shot_timer = 0.1; 74 | 75 | self.recoil_target_offset = Vector3::new(0.0, 0.0, -0.025); 76 | } 77 | } 78 | --------------------------------------------------------------------------------