7 releases
new 0.3.1 | Nov 20, 2024 |
---|---|
0.3.0 | Oct 15, 2024 |
0.1.4 | Jul 1, 2024 |
0.1.3 | Jun 23, 2024 |
0.1.0 | May 11, 2024 |
#365 in Game dev
135KB
580 lines
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);
Dependencies
~8–23MB
~376K SLoC