365 lines
13 KiB
Rust
365 lines
13 KiB
Rust
//! This scene encompasses all of the game where the player can walk around.
|
|
|
|
use chrono::{DateTime, Utc};
|
|
use nalgebra as na;
|
|
use raylib::prelude::*;
|
|
use std::time::SystemTime;
|
|
|
|
use crate::{
|
|
asset_manager::{load_music_from_internal_data, load_sound_from_internal_data},
|
|
discord::{DiscordChannel, DiscordRpcSignal},
|
|
global_resource_package::GlobalResources,
|
|
model::{
|
|
player::Player,
|
|
world_object::{ObjectCollider, WorldSpaceObjectCollider},
|
|
},
|
|
project_constants::ProjectConstants,
|
|
rendering::utilities::{anim_texture::AnimatedTexture, map_render::MapRenderer},
|
|
};
|
|
|
|
use super::main_menu::MenuStateSignal;
|
|
|
|
#[derive(Debug)]
|
|
pub struct PlayableScene {
|
|
pub has_updated_discord_rpc: bool,
|
|
player: Player,
|
|
world_map: MapRenderer,
|
|
camera: raylib::camera::Camera2D,
|
|
last_update: SystemTime,
|
|
game_soundtrack: Music,
|
|
world_colliders: Vec<WorldSpaceObjectCollider>,
|
|
show_debug_info: bool,
|
|
play_start_time: DateTime<Utc>,
|
|
}
|
|
|
|
impl PlayableScene {
|
|
/// Construct a new `PlayableScene`
|
|
pub fn new(
|
|
raylib_handle: &mut raylib::RaylibHandle,
|
|
thread: &raylib::RaylibThread,
|
|
constants: &ProjectConstants,
|
|
) -> Self {
|
|
let map_renderer = MapRenderer::new(
|
|
"map_gameMap.tmx",
|
|
"map_gameMap.objects.json",
|
|
raylib_handle,
|
|
thread,
|
|
)
|
|
.unwrap();
|
|
let world_colliders = map_renderer.get_world_colliders();
|
|
|
|
// Load the game music
|
|
let game_soundtrack =
|
|
load_music_from_internal_data(thread, "assets/audio/gameSoundtrack.mp3").unwrap();
|
|
|
|
Self {
|
|
has_updated_discord_rpc: false,
|
|
player: Player::new(na::Vector2::new(
|
|
10.0 * constants.tile_size as f32,
|
|
-10.0 * constants.tile_size as f32,
|
|
)),
|
|
world_map: map_renderer,
|
|
camera: raylib::camera::Camera2D {
|
|
target: raylib::math::Vector2 { x: 0.0, y: 0.0 },
|
|
offset: raylib::math::Vector2 { x: 0.0, y: 0.0 },
|
|
rotation: 0.0,
|
|
zoom: 1.0,
|
|
},
|
|
last_update: SystemTime::UNIX_EPOCH,
|
|
game_soundtrack,
|
|
world_colliders,
|
|
show_debug_info: false,
|
|
play_start_time: Utc::now(),
|
|
}
|
|
}
|
|
|
|
/// Handler for each frame
|
|
pub async fn render_frame(
|
|
&mut self,
|
|
raylib: &mut raylib::RaylibHandle,
|
|
rl_thread: &raylib::RaylibThread,
|
|
discord: &DiscordChannel,
|
|
global_resources: &GlobalResources,
|
|
constants: &ProjectConstants,
|
|
audio_subsystem: &mut RaylibAudio,
|
|
) -> MenuStateSignal {
|
|
// Handle updating discord RPC
|
|
if !self.has_updated_discord_rpc {
|
|
discord
|
|
.send(DiscordRpcSignal::BeginGameTimer)
|
|
.await
|
|
.unwrap();
|
|
discord
|
|
.send(DiscordRpcSignal::ChangeDetails {
|
|
details: "Playing the game".to_string(),
|
|
party_status: None,
|
|
})
|
|
.await
|
|
.unwrap();
|
|
self.has_updated_discord_rpc = true;
|
|
self.play_start_time = Utc::now();
|
|
}
|
|
|
|
// Ensure the game soundtrack is playing
|
|
if !audio_subsystem.is_music_playing(&self.game_soundtrack) {
|
|
debug!("Playing game soundtrack");
|
|
audio_subsystem.play_music_stream(&mut self.game_soundtrack);
|
|
} else {
|
|
audio_subsystem.update_music_stream(&mut self.game_soundtrack);
|
|
}
|
|
|
|
// Get a drawing handle
|
|
let mut draw = raylib.begin_drawing(rl_thread);
|
|
|
|
// Clear the screen
|
|
draw.clear_background(Color::WHITE);
|
|
|
|
self.draw_world(&mut draw, constants);
|
|
|
|
self.draw_ui(&mut draw, constants);
|
|
|
|
// NOTE: If you want to trigger a cutscene, do it here by using one of:
|
|
// return MenuStateSignal::DoFinishedCutscene {
|
|
// playtime: Utc::now().signed_duration_since(self.play_start_time),
|
|
// };
|
|
// return MenuStateSignal::DoMeltedDeathCutscene {
|
|
// playtime: Utc::now().signed_duration_since(self.play_start_time),
|
|
// };
|
|
// return MenuStateSignal::DoOceanCutscene {
|
|
// playtime: Utc::now().signed_duration_since(self.play_start_time),
|
|
// };
|
|
|
|
// A little hack to make pausing work
|
|
if draw.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
|
|
return MenuStateSignal::DoPauseMenu;
|
|
} else {
|
|
return MenuStateSignal::StartGame;
|
|
}
|
|
}
|
|
|
|
pub fn draw_world(&mut self, draw: &mut RaylibDrawHandle, constants: &ProjectConstants) {
|
|
// Begin camera mode
|
|
let mut ctx2d = draw.begin_mode2D(self.camera);
|
|
|
|
// Render the map
|
|
self.world_map.render_map(
|
|
&mut ctx2d,
|
|
&self.camera,
|
|
self.show_debug_info,
|
|
self.player.position,
|
|
);
|
|
|
|
let player_size =
|
|
(constants.tile_size as f32 * constants.player.start_size * self.player.size) as i32;
|
|
|
|
ctx2d.draw_rectangle(
|
|
self.player.position[0] as i32 - player_size / 2,
|
|
self.player.position[1] as i32 * -1 - player_size / 2,
|
|
player_size,
|
|
player_size,
|
|
Color::LIGHTBLUE,
|
|
);
|
|
}
|
|
|
|
pub fn draw_ui(&mut self, draw: &mut RaylibDrawHandle, constants: &ProjectConstants) {
|
|
// Obtain mouse position
|
|
let mouse_x = draw.get_mouse_x();
|
|
let mouse_y = draw.get_mouse_y();
|
|
|
|
// Optionally display debug info
|
|
if draw.is_key_pressed(KeyboardKey::KEY_F3) {
|
|
self.show_debug_info = !self.show_debug_info;
|
|
}
|
|
if self.show_debug_info {
|
|
// Draw FPS and mouse location
|
|
draw.draw_fps(10, 10);
|
|
draw.draw_text(
|
|
format!("Mouse position: ({}, {})", mouse_x, mouse_y).as_str(),
|
|
10,
|
|
30,
|
|
20,
|
|
Color::GREEN,
|
|
);
|
|
draw.draw_text(
|
|
format!("player: ({}, {}) size: {} map: ({}, {})", self.player.position.x,self.player.position.y,self.player.size,self.world_map.get_map_size().x,self.world_map.get_map_size().y).as_str(),
|
|
10,
|
|
50,
|
|
20,
|
|
Color::GREEN,
|
|
);
|
|
}
|
|
|
|
draw.draw_rectangle(draw.get_screen_width() / 2 - 225, 0, 450, 40, Color::WHITE);
|
|
draw.draw_text(
|
|
"Unregistered HyperCam 2",
|
|
draw.get_screen_width() / 2 - 215,
|
|
0,
|
|
32,
|
|
Color::BLACK,
|
|
);
|
|
}
|
|
|
|
// Physics
|
|
pub async fn update_physics(
|
|
&mut self,
|
|
raylib: &raylib::RaylibHandle,
|
|
constants: &ProjectConstants,
|
|
) {
|
|
// Get time since last physics update
|
|
let time = SystemTime::now();
|
|
let elapsed = time
|
|
.duration_since(self.last_update)
|
|
.expect("Time Appears to Have Moved Backwards!");
|
|
self.last_update = time;
|
|
let delta_time = elapsed.as_millis() as f32 / 1000.0; // Physics will be scaled by this value
|
|
|
|
let player = &mut self.player;
|
|
|
|
// NOTE: This is how to check friction and temperature
|
|
let current_friction = self.world_map.sample_friction_at(player.position);
|
|
let current_temperature = self.world_map.sample_temperature_at(player.position);
|
|
let map_size = self.world_map.get_map_size();
|
|
// TODO: You can access the colission list with: self.world_colliders
|
|
|
|
// Get input direction components
|
|
let h_axis = raylib.is_key_down(KeyboardKey::KEY_D) as i8
|
|
- raylib.is_key_down(KeyboardKey::KEY_A) as i8;
|
|
let v_axis = raylib.is_key_down(KeyboardKey::KEY_W) as i8
|
|
- raylib.is_key_down(KeyboardKey::KEY_S) as i8;
|
|
if h_axis != 0 || v_axis != 0 {
|
|
// Normalize input and accelerate in desired direction
|
|
let direction = na::Vector2::new(h_axis as f32, v_axis as f32).normalize();
|
|
player.velocity += &direction.xy()
|
|
* constants.player.acceleration as f32
|
|
* constants.tile_size as f32
|
|
* delta_time;
|
|
}
|
|
|
|
if player.velocity.magnitude() != 0.0 {
|
|
player.velocity -= player.velocity.normalize()
|
|
* constants.player.deceleration as f32
|
|
* constants.tile_size as f32
|
|
* delta_time;
|
|
if player.velocity.magnitude() < 1.0 {
|
|
player.velocity.set_magnitude(0.0);
|
|
}
|
|
}
|
|
|
|
if ((constants.player.max_velocity * constants.tile_size) as f32)
|
|
< player.velocity.magnitude()
|
|
{
|
|
player
|
|
.velocity
|
|
.set_magnitude((constants.player.max_velocity * constants.tile_size) as f32);
|
|
}
|
|
|
|
let velocity_modifier = &player.velocity * delta_time;
|
|
|
|
let player_size =
|
|
(constants.tile_size as f32 * constants.player.start_size * player.size / 2.0) as f32;
|
|
|
|
player.position.x += velocity_modifier.x;
|
|
|
|
for i in &self.world_colliders {
|
|
if player.position.x - player_size <= i.position.x + i.size.x / 2.0
|
|
&& player.position.x + player_size >= i.position.x + i.size.x / 2.0
|
|
&& player.position.y - player_size<= i.position.y + i.size.y / 2.0
|
|
&& player.position.y + player_size >= i.position.y + i.size.x / 2.0
|
|
{
|
|
if player.velocity.x < 0.0 {
|
|
player.position.x = i.position.x + i.size.x / 2.0 + player_size;
|
|
} else if player.velocity.x > 0.0 {
|
|
player.position.x = i.position.x - i.size.x / 2.0 - player_size;
|
|
}
|
|
|
|
player.velocity.x = 0.0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if player.position.x - player_size < 0.0 || player.position.x + player_size > self.world_map.get_map_size().x {
|
|
if player.velocity.x < 0.0 {
|
|
player.position.x = player_size;
|
|
} else if player.velocity.x > 0.0 {
|
|
player.position.x = self.world_map.get_map_size().x - player_size;
|
|
}
|
|
player.velocity.x = 0.0;
|
|
}
|
|
|
|
player.position.y += velocity_modifier.y;
|
|
|
|
for i in &self.world_colliders {
|
|
if player.position.x - player_size <= i.position.x + i.size.x / 2.0
|
|
&& player.position.x + player_size >= i.position.x + i.size.x / 2.0
|
|
&& player.position.y - player_size<= i.position.y + i.size.y / 2.0
|
|
&& player.position.y + player_size >= i.position.y + i.size.x / 2.0
|
|
{
|
|
if player.velocity.y < 0.0 {
|
|
player.position.y = i.position.y + i.size.y / 2.0 + player_size;
|
|
} else if player.velocity.x > 0.0 {
|
|
player.position.y = i.position.y - i.size.y / 2.0 - player_size;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if player.position.y + player_size > 0.0 || player.position.y - player_size < -self.world_map.get_map_size().y {
|
|
if player.velocity.y > 0.0 {
|
|
player.position.y = -player_size;
|
|
} else if player.velocity.y < 0.0 {
|
|
player.position.y = -self.world_map.get_map_size().y + player_size;
|
|
}
|
|
player.velocity.y = 0.0;
|
|
}
|
|
|
|
self.update_camera(raylib);
|
|
}
|
|
|
|
// Update the camera
|
|
pub fn update_camera(&mut self, raylib: &raylib::RaylibHandle) {
|
|
// Bounding box
|
|
let bbox = na::Vector2::new(0.2, 0.2);
|
|
|
|
// Get bounding box dimensions on the screen
|
|
let bbox_screen_min: raylib::math::Vector2 = (((na::Vector2::new(1.0, 1.0) - bbox) * 0.5)
|
|
.component_mul(&na::Vector2::new(
|
|
raylib.get_screen_width() as f32,
|
|
raylib.get_screen_height() as f32,
|
|
)))
|
|
.into();
|
|
let bbox_screen_max: raylib::math::Vector2 = (((na::Vector2::new(1.0, 1.0) + bbox) * 0.5)
|
|
.component_mul(&na::Vector2::new(
|
|
raylib.get_screen_width() as f32,
|
|
raylib.get_screen_height() as f32,
|
|
)))
|
|
.into();
|
|
|
|
// Get bounding box in world space
|
|
let mut bbox_world_min = raylib.get_screen_to_world2D(bbox_screen_min, self.camera);
|
|
let mut bbox_world_max = raylib.get_screen_to_world2D(bbox_screen_max, self.camera);
|
|
|
|
// Invert y
|
|
bbox_world_min.y *= -1.0;
|
|
bbox_world_max.y *= -1.0;
|
|
|
|
self.camera.offset = bbox_screen_min;
|
|
|
|
if self.player.position.x < bbox_world_min.x {
|
|
self.camera.target.x = self.player.position.x;
|
|
}
|
|
|
|
if self.player.position.y > bbox_world_min.y {
|
|
self.camera.target.y = -self.player.position.y;
|
|
}
|
|
|
|
if self.player.position.x > bbox_world_max.x {
|
|
self.camera.target.x = bbox_world_min.x + (self.player.position.x - bbox_world_max.x);
|
|
}
|
|
|
|
if self.player.position.y < bbox_world_max.y {
|
|
self.camera.target.y = bbox_world_max.y - (self.player.position.y + bbox_world_min.y);
|
|
}
|
|
}
|
|
}
|