# HeavylI Engine `HeavylI Engine` is a game engine (with graphics, ECS, and scripting support) based on the `HeavylI` graphics library. # Usage: This crate should be used with the `heavyli` (currently version 0.0.6) crate to get the best results from the engine. This engine includes ECS support (check `heavyli_engine::ecs`), native script support (check `heavyli_engine::ecs::native_script`), Lua scripting support (check `heavyli_engine::lua_script`), and basic sprite handling using Renderer2D and Sprite2D (check `heavyli::render`). # Code Example: First, checkout the [resources folder in the `heavyli` repository](https://gitlab.com/ovid.odedbe/heavyli/-/tree/main/heavyli/res) in order to load the images needed for this example. In this example we'll create a little mario game (no physics here though). To start, add this Lua script example at `res/test.lua` (see the resources folder in the repo): ```lua function start() math.randomseed(os.time()) -- Load new textures and save their IDs: mario_texture = add_texture("res/mario-stand.png") block_texture = add_texture("res/basic-block.png") -- Add new sprites: renderer:add_sprite(0, 0.0, 0.0, 0.5, 0.5, mario_texture) renderer:add_sprite(2, 1.0, 1.0, 0.5, 0.5, block_texture) renderer:add_sprite(3, 0.5, 1.0, 0.5, 0.5, block_texture) renderer:add_sprite(4, 1.0, 0.5, 0.5, 0.5, block_texture) renderer:add_sprite(5, 0.5, 0.5, 0.5, 0.5, block_texture) end counter = 6 pos_x = 0 pos_y = 0 speed = 1 mario_texture = 0 block_texture = 0 function update() speed = delta_time -- User input: if key_pressed("up") then pos_y = pos_y + speed elseif key_pressed("down") then pos_y = pos_y - speed end if key_pressed("left") then pos_x = pos_x + speed elseif key_pressed("right") then pos_x = pos_x - speed end renderer:set_sprite_position(0, pos_x, pos_y) renderer:set_camera_position(0, pos_x, pos_y) -- Randbom block generation: if key_pressed("a") then renderer:add_sprite(counter, counter % 12 * 0.5, math.random() % 30, 0.5, 0.5, block_texture) counter = counter + 1 print(counter) end end ``` Also, you should have these two shader files: `shader/basic_fragment.glsl`: ```c #version 330 core out vec4 FragColor; in vec3 ourColor; in vec2 texCoord; uniform sampler2D texture1; void main() { vec4 col = texture(texture1, texCoord) * vec4(ourColor, 1.0f); if (0 == col.r && 0 == col.g && 0 == col.b) { discard; } FragColor = col; } ``` `shader/basic_vertex.glsl`: ```c #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aColor; layout (location = 2) in vec2 aTexCoord; out vec3 ourColor; out vec2 texCoord; uniform mat4 translation; void main() { gl_Position = translation * vec4(aPos, 1.0); ourColor = aColor; texCoord = aTexCoord; } ``` With this script you'll have a little mario game running. Now, for the rust code: ```rust // Dependencies: extern crate glfw; extern crate heavyli_engine; extern crate nalgebra_glm as glm; // Modules: use heavyli_engine::{ ecs::{ scene::{SceneCore, SceneState, Update}, scene_manager::SceneManager, }, render::{ shader, window::Window, camera::Camera, renderer_2d::{generate_sprite_2d_buffers, generate_sprite_2d_vertices, Renderer2D}, utils::{init_glfw, configure_window, window_end_frame, window_start_frame}, }, }; use glfw::Glfw; use std::sync::{Arc, Mutex}; // Screen Size: const SCR_WIDTH: u32 = 800; const SCR_HEIGHT: u32 = 600; // Main Code: fn main() { // Initialize OpenGL with GLFW and create a new Window: let mut glfw = init_glfw(); let mut window = Window::new(&mut glfw, "Sandbox", SCR_WIDTH, SCR_HEIGHT); // Configure the new window: configure_window(&mut window); // Create a new Scene Manager: let mut scene_manager = SceneManager::new(Arc::new(Mutex::new(GameGlobals::new( &mut glfw, &mut window, )))); // Create a new scene: let scene1 = scene_manager.new_scene(Box::new(Scene1::new())); // Initial scene state: scene_manager.start_scene(scene1); // Run scene until it closes: while SceneState::End != scene_manager.get_scene_state(scene1).unwrap() { scene_manager.update_scene(scene1, 120.0); } } fn set_window_title(window: &mut Window, delta_time: f32) { let mut title = "Sandbox | FPS: ".to_string(); title.push_str( (1.0 / if 0.0 != delta_time && delta_time > 0.000001 { delta_time } else { f32::MIN_POSITIVE }) .to_string() .as_str(), ); window.set_title(&title); } // Create your globals for the Game: pub struct GameGlobals<'a> { pub glfw: &'a mut Glfw, pub window: &'a mut Window, } impl<'a> GameGlobals<'a> { pub fn new(glfw: &'a mut Glfw, window: &'a mut Window) -> Self { Self { glfw: glfw, // OpenGL - GLFW window: window, // Game's window } } } // Scene Loop Implementation: pub struct Scene1 { delta_count: f32, renderer: Option, // Adding Renderer2D to render sprites. } impl Scene1 { fn new() -> Self { Self { delta_count: 0.0, renderer: None, } } } impl<'a> Update> for Scene1 { fn start(&mut self, core: &mut SceneCore) { // Initialize renderer here: let mut vertices = generate_sprite_2d_vertices(); let shader_id = shader::compile("shaders/basic_vertex.glsl", "shaders/basic_fragment.glsl"); self.renderer = Some(Renderer2D::new( core.registry.clone(), generate_sprite_2d_buffers(&mut vertices), vertices, shader_id, )); // Add camera to the scene: core.registry.lock().unwrap().add_component( 0, Camera::new(glm::vec3(0.0, 0.0, -5.0), glm::vec2(0.0, 90.0)), ); // Create a new script handler: let script_id = core.lua_script_manager.create_script_handler(); // Load the test script: if let Err(err) = core .lua_script_manager .script_load(script_id, "res/test.lua") { println!("Error: {}", err); } if let Err(err) = core .lua_script_manager .load_renderer(script_id, self.renderer.as_ref().unwrap().clone()) { println!("Error: {}", err); } } fn update(&mut self, core: &mut SceneCore) { window_start_frame(core.globals.lock().unwrap().window); // Get camera view for world-location calculations: let cam_view = core .registry .lock() .unwrap() .get_component::(0) .unwrap() .borrow_mut() .lock() .unwrap() .get_view(); // Render all sprites: self.renderer .as_ref() .unwrap() .render(glm::vec2(SCR_WIDTH as f32, SCR_HEIGHT as f32), &cam_view); // Change the FPS count in title when 1 min passed: self.delta_count += core.delta_time; if self.delta_count >= 1.0 { set_window_title(core.globals.lock().unwrap().window, core.delta_time); self.delta_count = 0.0; } // End scene when window is closed: if !core.globals.lock().unwrap().window.is_open() { core.state = SceneState::End; } // IMPORTANT: remove all sprites' data at the end of the program: if SceneState::End == core.state { self.renderer.as_mut().unwrap().delete_all_sprites(); } // Poll IO Events: core.globals.lock().unwrap().glfw.poll_events(); // End window frame: window_end_frame(core.globals.lock().unwrap().window); } } ``` # Features: - ECS (Entity Component System) Support - Native Scripts support (NativeScript trait) - External Language Scripting - Lua Support (LuaScript struct) - Scene Support (Scene Manager, game globals) - Sound support (Sound Player) # Requirements: * cmake * make * rust * cargo # Windows Users - Compile using MSYS & MinGW: Make sure that the [`MSYS`](https://www.msys2.org/) tool is installed. Then follow these steps: 1. Update MSYS using: ``` pacman -Syuu ``` 2. Install a toolchain: a) for 64-bit: ``` pacman -S mingw-w64-x86_64-toolchain ``` b) for 32-bit: ``` pacman -S mingw-w64-i686-toolchain ``` For further information, check [this](https://stackoverflow.com/questions/30069830/how-to-install-mingw-w64-and-msys2) link. 3. Download the [GLFW pre-compiled binaries](https://www.glfw.org/download.html). 4. Put the banaries that satisfies your computer's bit architecture, and put them inside: ``` path/to/msys/mingw-your-version/lib ```