// External Crates: extern crate glfw; extern crate nalgebra_glm as glm; extern crate rand; // Modules Used: use std::sync::{Arc, Mutex}; use std::time::Instant; use heavyli_engine::{ math::{ projection_type::ProjectionType, transform::{self, Transform}, view, }, render::{ shader, shader::ShaderValue, shape::{ buffers, buffers::Buffers, shape_vertices::ShapeVertices, vertices::DrawType, vertices::Vertex, }, texture_2d::{self, TextureFilter}, utils::{self, init_glfw}, window::{ClearColor, Window}, }, sound::sound_player::SoundPlayer, }; // Screen Size: const SCR_WIDTH: u32 = 800; const SCR_HEIGHT: u32 = 600; // Utils: unsafe fn init_texture(texture_id: u32, texture_image_directory: &str, is_alpha: bool) { texture_2d::bind(texture_id); texture_2d::load_image(texture_image_directory.to_string(), is_alpha); texture_2d::unbind(); } unsafe fn init_buffers(buffers: &Buffers, vertices: &mut Vec) { buffers::bind_buffers(buffers); texture_2d::attach(vertices); // Attach the texture coordinates to fit the given vertices. buffers::bind_data(buffers.vao, vertices); buffers::update_vertex_attributes(DrawType::Triangles); // Let OpenGL know that the shape is based on Triangles. buffers::unbind(); } // Stores General Data of Objects in Game. struct Object { tid: u32, sid: u32, verts: Vec, buff: Buffers, trans: Transform, overlap_with: i32, closest_to: usize, closest_dis: f32, } // Basic Parameters for Transform and Overlapping: const Z_NEAR: f32 = 0.1; const Z_FAR: f32 = 100.0; const FOV: f32 = 45.0; const ORTHO_DISTANCE: f32 = 1.75; const NO_OVERLAP: i32 = -1; impl Object { pub fn new(position: glm::Vec3, scale: glm::Vec3, image: &str) -> Self { let buffers = unsafe { buffers::generate_buffers() }; let shader_id = shader::compile("shaders/basic_vertex.glsl", "shaders/basic_fragment.glsl"); let texture_id = unsafe { texture_2d::generate_texture_id(TextureFilter::Pixelated) }; let mut vertices = ShapeVertices::Rectangle(glm::vec2(0.5, 0.5), glm::vec2(-0.5, -0.5)).value(); // Create Vertices Based on Rectangle. unsafe { init_buffers(&buffers, &mut vertices); } unsafe { init_texture(texture_id, image, false); } // Set Transform Position and Scale: let mut transform = transform::create_transform_by_position(position); transform.scale = scale; // Initialize Object: return Object { tid: texture_id, sid: shader_id, verts: vertices, buff: buffers, trans: transform, overlap_with: NO_OVERLAP, closest_to: 0, closest_dis: f32::INFINITY, }; } pub fn new_collider(object: &Object) -> Self { return Object { tid: 0, sid: 0, verts: object.verts.clone(), buff: Buffers { vao: 0, vbo: 0 }, trans: object.trans, overlap_with: NO_OVERLAP, closest_to: 0, closest_dis: f32::INFINITY, }; } pub unsafe fn render(&self, view: &glm::Mat4) { texture_2d::bind(self.tid); // Set Transformation in Shader: shader::use_program(self.sid); shader::set_value( self.sid, "translation", ShaderValue::Mat4(transform::translate( &self.trans, Z_NEAR, Z_FAR, FOV, *view, glm::vec2(SCR_WIDTH as f32, SCR_HEIGHT as f32), ORTHO_DISTANCE, ProjectionType::Orthographic, )), ); // Render: buffers::bind_vao(self.buff.vao); utils::draw_arrays(DrawType::Triangles, self.verts.len() as i32); } pub fn is_overlap(&self, other: &Object) -> bool { return self.is_overlap_x(other) && self.is_overlap_y(other); // Check both X and Y axises separately. } pub fn is_overlap_x(&self, other: &Object) -> bool { return self.trans.position.x + self.verts[0].position.x * self.trans.scale.x // Self Left >= other.trans.position.x + other.verts[2].position.x * other.trans.scale.x // Other Right && self.trans.position.x + self.verts[2].position.x * self.trans.scale.x // Self Right <= other.trans.position.x + other.verts[0].position.x * other.trans.scale.x; // Other Left } pub fn is_overlap_y(&self, other: &Object) -> bool { return self.trans.position.y + self.verts[0].position.y * self.trans.scale.y // Self Top >= other.trans.position.y + other.verts[2].position.y * other.trans.scale.y // Other Bottom && self.trans.position.y + self.verts[2].position.y * self.trans.scale.y // Self Bottom <= other.trans.position.y + other.verts[0].position.y * other.trans.scale.y; // Other Top } pub fn update_interactions(&mut self, index: usize, objects: &Vec>>) { // Go through all Objects: for i in 0..objects.len() { // Check if it's not the Checking Object: if index != i { // Get Closest Object and its distance: let distance = self.distance_squared(&objects[i].lock().unwrap()); if distance < self.closest_dis { self.closest_to = i; self.closest_dis = distance; } // Get the Object Overlapping with: if self.is_overlap(&objects[i].lock().unwrap()) { self.overlap_with = i as i32; return; } } } // Default Reset: self.overlap_with = NO_OVERLAP; } pub fn distance_y(&self, other: &Object) -> f32 { return self.trans.position.y - other.trans.position.y; } pub fn distance_squared(&self, other: &Object) -> f32 { return (self.trans.position.x - other.trans.position.x).powf(2.0) + (self.trans.position.y - other.trans.position.y).powf(2.0); // Pythagoras theorem squared. } // Should be explicitly called: pub fn delete(&mut self) { unsafe { // Free VBO, VAO, and the Texture ID: buffers::delete_buffers(&mut self.buff); texture_2d::delete_texture(&mut self.tid); } } } fn main() { let mut glfw = init_glfw(); let mut window = Window::new(&mut glfw, "Mario Clone", SCR_WIDTH, SCR_HEIGHT); let mut objects = Vec::>>::new(); let mut pos = glm::vec3(0.0, 0.0, -5.0); let rot = glm::vec2(0.0, 90.0); let mut vel = glm::vec2(0.0, 0.0); window.make_current(); window.set_key_polling(true); window.set_framebuffer_size_polling(true); window.load_function_pointers(); objects.push(Arc::new(Mutex::new(Object::new( glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.25, 0.25, 1.0), "res/mario-stand.png", )))); objects.push(Arc::new(Mutex::new(Object::new( glm::vec3(0.0, -0.5, 0.0), glm::vec3(4.0, 0.25, 1.0), "res/basic-block.png", )))); objects.push(Arc::new(Mutex::new(Object::new( glm::vec3(-0.25, 0.5, 0.0), glm::vec3(4.0, 0.25, 1.0), "res/basic-block.png", )))); let mut delta_time = 0.0; let mut delta_count = 0.0; let mut height: f32 = 0.0; let mut pause = false; let mut can_jump = true; let mut pclone = Object::new_collider(&objects[0].lock().unwrap()); let mut sound_player = SoundPlayer::new(); let engine_index = sound_player.create_engine(); sound_player.load_sound(engine_index, "res/mario-level1.mp3"); while window.is_open() { let start_time = Instant::now(); // Replay: if sound_player.no_sound(engine_index) { sound_player.load_sound(engine_index, "res/mario-level1.mp3"); } window.process_events(); unsafe { Window::clear(ClearColor { red: 0.3, green: 0.5, blue: 1.0, alpha: 1.0, }); } if window.key_pressed(glfw::Key::Enter) { pause = !pause; } /*if window.key_pressed(glfw::Key::Escape) { window.close(); }*/ // TODO let view = view::get_view_matrix(&pos, rot.x, rot.y); for i in 0..objects.len() { objects[i].lock().unwrap().update_interactions(i, &objects); } if !pause { if delta_time < 0.0001 { delta_time = 0.0001 } else if delta_time > 0.5 { delta_time = 0.5; } player_input( delta_time, &window, &mut objects[0].lock().unwrap(), &mut pclone, &mut vel, &objects, &mut height, &mut can_jump, ); pos.x = objects[0].lock().unwrap().trans.position.x; pos.y = objects[0].lock().unwrap().trans.position.y; } for object in objects.iter() { let o_ref = &mut object.lock().unwrap(); unsafe { o_ref.render(&view) }; o_ref.overlap_with = NO_OVERLAP; o_ref.closest_to = 0; o_ref.closest_dis = f32::INFINITY; } window.swap_buffers(); glfw.poll_events(); utils::limit_fps(start_time, 120.0); delta_time = start_time.elapsed().as_nanos() as f32 / 1000000000.0; delta_count += delta_time; if delta_count >= 1.0 { let mut title = "Mario Clone | 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); delta_count = 0.0; } } for object in objects.iter() { object.lock().unwrap().delete(); } } fn player_input( delta_time: f32, window: &Window, player: &mut Object, pclone: &mut Object, vel: &mut glm::Vec2, objects: &Vec>>, height: &mut f32, can_jump: &mut bool, ) { const MOVE_SPEED: f32 = 2.0; const JUMP_SPEED: f32 = 10.0; const MAX_RUN_SPEED: f32 = 1.25; const MAX_MOVE_SPEED: f32 = 1.0; const FRACTION_X: f32 = 0.25; const FRACTION_Y: f32 = 1.0; const GRAVITY: f32 = 4.0; let mut acc = glm::vec2(0.0, 0.0); if vel.x.abs() < if window.key_pressed(glfw::Key::LeftShift) { MAX_RUN_SPEED } else { MAX_MOVE_SPEED } { // X Input: if window.key_pressed(glfw::Key::D) || window.key_pressed(glfw::Key::Right) { acc.x = -MOVE_SPEED; } else if window.key_pressed(glfw::Key::A) || window.key_pressed(glfw::Key::Left) { acc.x = MOVE_SPEED; } else { if vel.x.abs() > 0.0 { acc.x -= vel.x / vel.x.abs(); } } } else { vel.x = (vel.x / vel.x.abs()) * if window.key_pressed(glfw::Key::LeftShift) { MAX_RUN_SPEED } else { MAX_MOVE_SPEED }; } // Set Facing: if vel.x < -0.1 { player.trans.rotation.y = 0.0; } else if vel.x > 0.1 { player.trans.rotation.y = -180.0; } if true == *can_jump { // Y Input: if window.key_pressed(glfw::Key::W) || window.key_pressed(glfw::Key::Up) { acc.y = JUMP_SPEED; *height += delta_time; } else if window.key_released(glfw::Key::W) || window.key_released(glfw::Key::Up) { *can_jump = false; } } const MAX_HEIGHT: f32 = 0.5; if *height > MAX_HEIGHT { *can_jump = false; } let vel_norm = if vel.x.abs() > 0.0 || vel.y.abs() > 0.0 { vel.normalize() } else { glm::vec2(0.0, 0.0) }; acc.x -= vel_norm.x * FRACTION_X; acc.y -= vel_norm.y * FRACTION_Y; acc.y -= GRAVITY; let end_offset = glm::vec2( vel.x * delta_time + acc.x * delta_time * delta_time / 2.0, vel.y * delta_time + acc.y * delta_time * delta_time / 2.0, ); let original_pos = player.trans.position; let mut pos = original_pos; pos.y += end_offset.y; pclone.trans.position = pos; pclone.update_interactions(0, objects); // If Colliding, Revert Action: if NO_OVERLAP == pclone.overlap_with { player.trans.position.y += end_offset.y; vel.y += acc.y * delta_time; } else { if player.closest_to != 0 { let distance_y = pclone.distance_y(&objects[pclone.overlap_with as usize].lock().unwrap()); if distance_y >= 0.0 { *height = 0.0; *can_jump = true; } } if vel.y > 0.0 { *height = 0.0; *can_jump = false; } vel.y = 0.0; } pos = original_pos; pos.x += end_offset.x; pclone.trans.position = pos; pclone.update_interactions(0, objects); // If Colliding, Revert Action: if NO_OVERLAP == pclone.overlap_with { player.trans.position.x += end_offset.x; vel.x += acc.x * delta_time; } else { vel.x = 0.0; } }