thin-engine

Crates.iothin-engine
lib.rsthin-engine
version0.3.1
sourcesrc
created_at2024-05-11 00:28:17.394886
updated_at2024-11-20 00:04:30.657313
descriptionA thin engine between glium, winit, glium-types and winit-input-map with some extra helper functions
homepage
repositoryhttps://github.com/abyss-witch/thin-engine
max_upload_size
id1236453
size297,069
abyss-witch (abss-witch)

documentation

README

Thin Engine

A thin game engine (hence the name) which ties some of my libraries (winit-input-map and glium-types) into an easy to use, low boilerplate and high control game engine. Best suited for small projects.

Features: - Gamepad Support - Variable Input Support - Drawing through glium - Prebuilt shaders - Data Types for everything in glsl - Quaternions - Prebuilt meshes - optional text renderer

use std::f32::consts::PI;
use thin_engine::{prelude::*, meshes::teapot};
use draw_parameters::BackfaceCullingMode;
#[derive(Hash, PartialEq, Eq, Clone, Copy)]
enum Action {
    Jump, Exit,
    Left, Right, Forward, Back,
    LookUp, LookDown, LookLeft, LookRight
}
use Action::*;

let (event_loop, window, display) = thin_engine::set_up().unwrap();
window.set_title("Walk Test");
window.set_cursor_grab(CursorGrabMode::Confined).ok();
window.set_cursor_grab(CursorGrabMode::Locked).ok();
window.set_cursor_visible(false);

let input = {
    use thin_engine::input_map_setup::*;
    input_map!(
        (Jump,    Space,  GamepadButton::South),
        (Exit,    Escape, GamepadButton::Start),
        (Left,    ArrowLeft,  KeyA,  Axis(LeftStickX,  Neg)),
        (Right,   ArrowRight, KeyD,  Axis(LeftStickX,  Pos)),
        (Forward, ArrowUp,    KeyW,  Axis(LeftStickY,  Neg)),
        (Back,    ArrowDown,  KeyS,  Axis(LeftStickY,  Pos)),
        (LookRight, MouseMoveX(Pos), Axis(RightStickX, Pos)),
        (LookLeft,  MouseMoveX(Neg), Axis(RightStickX, Neg)),
        (LookUp,    MouseMoveY(Pos), Axis(RightStickY, Neg)),
        (LookDown,  MouseMoveY(Neg), Axis(RightStickY, Pos))
    )
};

let (indices, verts, norms) = mesh!(
    &display, &teapot::INDICES,
    &teapot::VERTICES, &teapot::NORMALS
);
let draw_parameters = DrawParameters {
    backface_culling: BackfaceCullingMode::CullClockwise,
    ..params::alias_3d()
};
let program = Progronam::from_source(
    &display, shaders::VERTEX,
    "#version 140
    out vec4 colour;
    in vec3 v_normal;
    uniform vec3 light;
    const vec3 albedo = vec3(0.1, 1.0, 0.3);
    void main(){
        float light_level = dot(light, v_normal);
        colour = vec4(albedo * light_level, 1.0);
    }", None,
).unwrap();

let mut pos = vec3(0.0, 0.0, -30.0);
let mut rot = vec2(0.0, 0.0);
let mut gravity = 0.0;

let mut frame_start = Instant::now();
let settings = Settings::from_fps(60);
thin_engine::run(event_loop, input, settings, |input, _, target| {
    let delta_time = frame_start.elapsed().as_secs_f32();
    frame_start = Instant::now();

    let size = window.inner_size().into();
    display.resize(size);
    let mut frame = display.draw();
    let view = Mat4::view_matrix_3d(size, 1.0, 1024.0, 0.1);

    //handle gravity and jump
    gravity += delta_time * 9.5;
    if input.pressed(Jump) {
        gravity = -10.0;
    }

    //set camera rotation
    let look_move = input.dir(LookRight, LookLeft, LookUp, LookDown);
    rot += look_move.scale(delta_time * 7.0);
    rot.y = rot.y.clamp(-PI / 2.0, PI / 2.0);
    let rx = Quaternion::from_y_rot(rot.x);
    let ry = Quaternion::from_x_rot(rot.y);
    let rot = rx * ry;

    //move player based on view and gravity
    let dir = input.dir_max_len_1(Right, Left, Forward, Back);
    let dir =  vec3(dir.x, 0.0, dir.y);
    pos += dir.transform(&rx.into()).scale(5.0*delta_time);
    pos.y = (pos.y - gravity * delta_time).max(0.0);

    if input.pressed(Exit) { target.exit() }

    frame.clear_color_and_depth((0.0, 0.0, 0.0, 1.0), 1.0);
    //draw teapot
    frame.draw(
        (&verts, &norms), &indices,
        &program, &uniform! {
            view: view,
            model: Mat4::from_scale(Vec3::splat(0.1)),
            camera: Mat4::from_inverse_transform(pos, Vec3::ONE, rot),
            light: vec3(1.0, -0.9, -1.0).normalise()
        },
        &draw_parameters,
    ).unwrap();

    frame.finish().unwrap();
}).unwrap();

Recomended Drawing Abstraction

It is encouraged to make structs for abstracting drawing that is used often. A good way to structure the data is like so:

pub struct Drawer<'a> {
    pub display: &'a thin_engine::Display,
    pub vertices: &'a VertexBuffer<Vertex>,
    pub indices: &'a IndexBuffer<u32>,
    pub shader: &'a Program,
}
impl Drawerer {
    pub fn draw(&self, &mut impl glium::Surface) -> Result<(), glium::DrawError> {
        // your drawing code
    }
}

and initialising the data like this:

let drawerer = Drawer {
    pub display: &display,
    pub vertices: &vertices,
    pub indices: &indices,
    pub shader: &shader
}

this allows for reuse of the display and even shaders and meshes while still removing boilerplate

Optional Text Renderer

If you want to use the text renderer enable the text feature by adding this to your Cargo.toml

[dependencies]
thin-engine = { version = "*", features = ["text"] }

The text renderer can be setup like this

use thin_engine::{prelude::*, text_renderer::*};

let (indices, vertices, uvs) = Font::mesh(&display);
let text_shader = Font::shader(&display).unwrap();
let draw_params = DrawParameters {
    blend: glium::Blend::alpha_blending(),
    ..Default::default()
};

let mut font = Font::from_scale_and_file(40.0, "file/location").unwrap();
let text_renderer = TextRenderer {
    shader: &text_shaderm indices: &indices, vertices: &vertices,
    uv: &uvs, draw_params: &draw_params, display: &display
};

and then can be drawn in the loop like so

// draw the sentence Hello World in green text
text_renderer.draw(
    "Hello World", vec3(0.0, 1.0, 0.0), &mut frame,
    model_mat, view_mat, camera_mat, &mut font
).unwrap();

for wrapping and tab spacing of text you can use

font.format_text(text, Some(wrap), tab_indent, &display);
Commit count: 14

cargo fmt