6 releases

0.0.7 Nov 1, 2022
0.0.5 Sep 1, 2022
0.0.4 Aug 8, 2022
0.0.2 Jul 21, 2022

#936 in Game dev

MIT license

5.5MB
2K SLoC

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 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):

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:

#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:

#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:

// 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<Renderer2D>, // Adding Renderer2D to render sprites.
}

impl Scene1 {
    fn new() -> Self {
        Self {
            delta_count: 0.0,
            renderer: None,
        }
    }
}

impl<'a> Update<GameGlobals<'a>> for Scene1 {
    fn start(&mut self, core: &mut SceneCore<GameGlobals>) {
        // 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<GameGlobals>) {
        window_start_frame(core.globals.lock().unwrap().window);

        // Get camera view for world-location calculations:
        let cam_view = core
            .registry
            .lock()
            .unwrap()
            .get_component::<Camera>(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 tool is installed. Then follow these steps:

  1. Update MSYS using:
pacman -Syuu
  1. 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 link.

  1. Download the GLFW pre-compiled binaries.

  2. Put the banaries that satisfies your computer's bit architecture, and put them inside:

path/to/msys/mingw-your-version/lib

Dependencies

~11–44MB
~635K SLoC