Merge remote-tracking branch 'origin/master' into assets
This commit is contained in:
commit
4f09f8e6e6
@ -6,7 +6,9 @@ edition = "2018"
|
|||||||
description = ""
|
description = ""
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
raylib = { version = "3.5", git = "https://github.com/ewpratten/raylib-rs", branch = "master" }
|
raylib = { version = "3.5", git = "https://github.com/ewpratten/raylib-rs", branch = "master", features = [
|
||||||
|
"with_serde"
|
||||||
|
] }
|
||||||
serialstudio = "0.1.0"
|
serialstudio = "0.1.0"
|
||||||
serde = "1.0.125"
|
serde = "1.0.125"
|
||||||
serde_json = "1.0.64"
|
serde_json = "1.0.64"
|
||||||
@ -14,3 +16,6 @@ failure = "0.1.8"
|
|||||||
parry2d = "0.4.0"
|
parry2d = "0.4.0"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
env_logger = "0.8.3"
|
env_logger = "0.8.3"
|
||||||
|
nalgebra = "0.26.1"
|
||||||
|
rand = "0.8.3"
|
||||||
|
tiled = "0.9.4"
|
||||||
|
1
assets/.gitignore
vendored
Normal file
1
assets/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
savestate.json
|
16
assets/worlds/mainworld.json
Normal file
16
assets/worlds/mainworld.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"end_position": {
|
||||||
|
"x": 10000.0,
|
||||||
|
"y": 10000.0
|
||||||
|
},
|
||||||
|
"fish": [
|
||||||
|
{
|
||||||
|
"x": 500.0,
|
||||||
|
"y": 300.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 800.0,
|
||||||
|
"y": 200.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
133
src/entities/fish.rs
Normal file
133
src/entities/fish.rs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
use rand::{Rng, prelude::ThreadRng};
|
||||||
|
use raylib::prelude::*;
|
||||||
|
|
||||||
|
use crate::{gamecore::GameCore, lib::utils::triangles::rotate_vector, player::Player};
|
||||||
|
|
||||||
|
const FISH_FOLLOW_PLAYER_DISTANCE: f32 = 30.0;
|
||||||
|
const FISH_FOLLOW_PLAYER_SPEED: f32 = 1.8;
|
||||||
|
const FISH_FOLLOW_PLAYER_SPEED_FAST: f32 = FISH_FOLLOW_PLAYER_SPEED * 3.0;
|
||||||
|
const FISH_ATTACH_RADIUS: f32 = 20.0;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FishEntity {
|
||||||
|
position: Vector2,
|
||||||
|
direction: Vector2,
|
||||||
|
pub following_player: bool,
|
||||||
|
size: Vector2,
|
||||||
|
rng: ThreadRng
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FishEntity {
|
||||||
|
pub fn new(position: Vector2) -> Self {
|
||||||
|
Self {
|
||||||
|
position: position,
|
||||||
|
direction: Vector2::zero(),
|
||||||
|
following_player: false,
|
||||||
|
size: Vector2 { x: 5.0, y: 8.0 },
|
||||||
|
rng: rand::thread_rng()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_from_positions(positions: &Vec<Vector2>) -> Vec<Self> {
|
||||||
|
let mut output = Vec::new();
|
||||||
|
for position in positions {
|
||||||
|
output.push(FishEntity::new(*position));
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_follow_player(&mut self, player: &Player, dt: f64) {
|
||||||
|
// Distance and direction to player
|
||||||
|
let dist_to_player = player.position - self.position;
|
||||||
|
let dist_to_player_lin = self.position.distance_to(player.position);
|
||||||
|
let mut direction_to_player = dist_to_player;
|
||||||
|
direction_to_player.normalize();
|
||||||
|
|
||||||
|
// Fish movement
|
||||||
|
let movement;
|
||||||
|
|
||||||
|
// Random variance
|
||||||
|
let variance = self.rng.gen_range(500.0..1000.0) / 1000.0;
|
||||||
|
|
||||||
|
// If the fish is double its follow distance from the player
|
||||||
|
if dist_to_player_lin.abs() > (FISH_FOLLOW_PLAYER_DISTANCE * 2.0) {
|
||||||
|
movement = direction_to_player * FISH_FOLLOW_PLAYER_SPEED_FAST * variance;
|
||||||
|
} else {
|
||||||
|
// Move slowly in the direction of the player unless too close
|
||||||
|
if dist_to_player_lin.abs() > FISH_FOLLOW_PLAYER_DISTANCE {
|
||||||
|
movement = direction_to_player * FISH_FOLLOW_PLAYER_SPEED * variance;
|
||||||
|
} else {
|
||||||
|
movement = Vector2::zero();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the fish
|
||||||
|
self.direction = direction_to_player;
|
||||||
|
self.position += movement;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_free_movement(&mut self, player: &mut Player, dt: f64) {
|
||||||
|
// Distance and direction to player
|
||||||
|
let dist_to_player = player.position - self.position;
|
||||||
|
let dist_to_player_lin = self.position.distance_to(player.position);
|
||||||
|
let mut direction_to_player = dist_to_player;
|
||||||
|
direction_to_player.normalize();
|
||||||
|
|
||||||
|
// Handle player picking up fish
|
||||||
|
if player.position.distance_to(self.position).abs() <= player.size.y * 2.2 {
|
||||||
|
self.following_player = true;
|
||||||
|
|
||||||
|
// Add currency to the player
|
||||||
|
player.coins += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look at the player;
|
||||||
|
self.position = self.position;
|
||||||
|
self.direction = direction_to_player;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_position(&mut self, player: &mut Player, dt: f64) {
|
||||||
|
if self.following_player {
|
||||||
|
self.handle_follow_player(player, dt);
|
||||||
|
} else {
|
||||||
|
self.handle_free_movement(player, dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(&self, context_2d: &mut RaylibMode2D<RaylibDrawHandle>) {
|
||||||
|
// Direction
|
||||||
|
let direction =
|
||||||
|
Vector2::zero().angle_to(self.direction.normalized()) + (90.0 as f32).to_radians();
|
||||||
|
|
||||||
|
// Get the corners of the fish
|
||||||
|
let fish_front = rotate_vector(
|
||||||
|
Vector2 {
|
||||||
|
x: 0.0,
|
||||||
|
y: (self.size.y / 2.0) * -1.0,
|
||||||
|
},
|
||||||
|
direction,
|
||||||
|
);
|
||||||
|
let fish_bl = rotate_vector(
|
||||||
|
Vector2 {
|
||||||
|
x: (self.size.x / 2.0) * -1.0,
|
||||||
|
y: (self.size.y / 2.0),
|
||||||
|
},
|
||||||
|
direction,
|
||||||
|
);
|
||||||
|
let fish_br = rotate_vector(
|
||||||
|
Vector2 {
|
||||||
|
x: (self.size.x / 2.0),
|
||||||
|
y: (self.size.y / 2.0),
|
||||||
|
},
|
||||||
|
direction,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw the fish as a triangle with rotation
|
||||||
|
context_2d.draw_triangle(
|
||||||
|
self.position + fish_front,
|
||||||
|
self.position + fish_bl,
|
||||||
|
self.position + fish_br,
|
||||||
|
Color::BLACK,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
1
src/entities/mod.rs
Normal file
1
src/entities/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod fish;
|
@ -1,14 +1,16 @@
|
|||||||
//! This file contains the global state of the game. Data here is passed around to all handler functions.
|
//! This file contains the global state of the game. Data here is passed around to all handler functions.
|
||||||
|
|
||||||
use std::fmt;
|
use std::{fmt, fs::File, io::BufReader};
|
||||||
|
|
||||||
use raylib::{
|
use raylib::{
|
||||||
camera::Camera2D, math::Vector2, prelude::RaylibDrawHandle, RaylibHandle, RaylibThread,
|
camera::Camera2D, math::Vector2, prelude::RaylibDrawHandle, RaylibHandle, RaylibThread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::resources::GlobalResources;
|
use crate::{items::ShopItems, player::Player, resources::GlobalResources, world::World};
|
||||||
|
|
||||||
|
use failure::Error;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Overall states for the game
|
/// Overall states for the game
|
||||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||||
@ -16,7 +18,9 @@ pub enum GameState {
|
|||||||
Loading,
|
Loading,
|
||||||
MainMenu,
|
MainMenu,
|
||||||
PauseMenu,
|
PauseMenu,
|
||||||
GameQuit
|
GameQuit,
|
||||||
|
InGame,
|
||||||
|
GameEnd,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for GameState {
|
impl fmt::Display for GameState {
|
||||||
@ -25,12 +29,58 @@ impl fmt::Display for GameState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||||
|
pub struct GameProgress {
|
||||||
|
coins: u32,
|
||||||
|
max_depth: f32,
|
||||||
|
fastest_time: Option<f64>,
|
||||||
|
inventory: Vec<ShopItems>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameProgress {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_file(file: String) -> Result<Self, Error> {
|
||||||
|
// Load the file
|
||||||
|
let file = File::open(file)?;
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
Ok(serde_json::from_reader(reader)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_from_file(file: String) -> Self {
|
||||||
|
// Load from file
|
||||||
|
let loaded = GameProgress::from_file(file);
|
||||||
|
if loaded.is_ok() {
|
||||||
|
return loaded.unwrap();
|
||||||
|
} else {
|
||||||
|
return GameProgress::new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_file(&self, file: String) -> Result<(), Error> {
|
||||||
|
// Serialize
|
||||||
|
let json = serde_json::to_string(self)?;
|
||||||
|
|
||||||
|
// Write to file
|
||||||
|
std::fs::write(file, json)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This structure contains the entire game state, and should be passed around to various logic functions.
|
/// This structure contains the entire game state, and should be passed around to various logic functions.
|
||||||
pub struct GameCore {
|
pub struct GameCore {
|
||||||
/// The game's overall state
|
/// The game's overall state
|
||||||
pub state: GameState,
|
pub state: GameState,
|
||||||
pub last_state: GameState,
|
pub last_state: GameState,
|
||||||
pub last_state_change_time: f64,
|
pub last_state_change_time: f64,
|
||||||
|
pub last_frame_time: f64,
|
||||||
pub has_rendered_first_frame: bool,
|
pub has_rendered_first_frame: bool,
|
||||||
|
|
||||||
/// Resources
|
/// Resources
|
||||||
@ -40,15 +90,28 @@ pub struct GameCore {
|
|||||||
pub master_camera: Camera2D,
|
pub master_camera: Camera2D,
|
||||||
|
|
||||||
/// Debug features
|
/// Debug features
|
||||||
pub show_simple_debug_info: bool
|
pub show_simple_debug_info: bool,
|
||||||
|
|
||||||
|
/// The world
|
||||||
|
pub world: World,
|
||||||
|
|
||||||
|
/// The player
|
||||||
|
pub player: Player,
|
||||||
|
pub progress: GameProgress,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameCore {
|
impl GameCore {
|
||||||
pub fn new(raylib: &mut RaylibHandle, thread: &RaylibThread) -> Self {
|
pub fn new(
|
||||||
|
raylib: &mut RaylibHandle,
|
||||||
|
thread: &RaylibThread,
|
||||||
|
world: World,
|
||||||
|
progress: GameProgress,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: GameState::Loading,
|
state: GameState::Loading,
|
||||||
last_state: GameState::Loading,
|
last_state: GameState::Loading,
|
||||||
last_state_change_time: 0.0,
|
last_state_change_time: 0.0,
|
||||||
|
last_frame_time: 0.0,
|
||||||
has_rendered_first_frame: false,
|
has_rendered_first_frame: false,
|
||||||
resources: GlobalResources::load_all(raylib, thread)
|
resources: GlobalResources::load_all(raylib, thread)
|
||||||
.expect("Failed to load game assets. Can not launch!"),
|
.expect("Failed to load game assets. Can not launch!"),
|
||||||
@ -56,9 +119,12 @@ impl GameCore {
|
|||||||
offset: Vector2::zero(),
|
offset: Vector2::zero(),
|
||||||
target: Vector2::zero(),
|
target: Vector2::zero(),
|
||||||
rotation: 0.0,
|
rotation: 0.0,
|
||||||
zoom: 1.0,
|
zoom: 2.0,
|
||||||
},
|
},
|
||||||
show_simple_debug_info: false
|
show_simple_debug_info: false,
|
||||||
|
world: world,
|
||||||
|
player: Player::new(),
|
||||||
|
progress: progress,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
src/items.rs
Normal file
10
src/items.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "t", content = "c")]
|
||||||
|
pub enum ShopItems {
|
||||||
|
StunGun(u8),
|
||||||
|
AirBag,
|
||||||
|
Flashlight(u8),
|
||||||
|
Flippers(u8)
|
||||||
|
}
|
@ -1 +1,2 @@
|
|||||||
pub mod profiler;
|
pub mod profiler;
|
||||||
|
pub mod triangles;
|
@ -16,7 +16,12 @@ pub struct ProfilerData {
|
|||||||
pub active_sounds: i32,
|
pub active_sounds: i32,
|
||||||
|
|
||||||
// Game core
|
// Game core
|
||||||
pub game_state: String
|
pub game_state: String,
|
||||||
|
|
||||||
|
// Player
|
||||||
|
pub player_coins: u32,
|
||||||
|
pub player_boost_percent: f32,
|
||||||
|
pub player_breath_percent: f32
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The development profiler
|
/// The development profiler
|
||||||
@ -117,6 +122,33 @@ impl GameProfiler {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
DataGroup {
|
||||||
|
title: "Player".to_string(),
|
||||||
|
widget_type: None,
|
||||||
|
datasets: vec![
|
||||||
|
DataSet {
|
||||||
|
title: Some("Coins".to_string()),
|
||||||
|
value: json!(self.data.player_coins),
|
||||||
|
graph: Some(false),
|
||||||
|
unit: Some("coins".to_string()),
|
||||||
|
w_type: None,
|
||||||
|
},
|
||||||
|
DataSet {
|
||||||
|
title: Some("Boost".to_string()),
|
||||||
|
value: json!(self.data.player_boost_percent),
|
||||||
|
graph: Some(false),
|
||||||
|
unit: Some("%".to_string()),
|
||||||
|
w_type: None,
|
||||||
|
},
|
||||||
|
DataSet {
|
||||||
|
title: Some("Breath".to_string()),
|
||||||
|
value: json!(self.data.player_breath_percent),
|
||||||
|
graph: Some(false),
|
||||||
|
unit: Some("%".to_string()),
|
||||||
|
w_type: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
14
src/lib/utils/triangles.rs
Normal file
14
src/lib/utils/triangles.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use raylib::math::Vector2;
|
||||||
|
|
||||||
|
|
||||||
|
pub fn rotate_vector(vector: Vector2, angle_rad: f32) -> Vector2{
|
||||||
|
|
||||||
|
// let dist = (vector.x * vector.x) + (vector.y * vector.y);
|
||||||
|
// let angle = (vector.x.abs() / vector.y.abs()).atan();
|
||||||
|
// let angle = angle + angle_rad;
|
||||||
|
return Vector2 {
|
||||||
|
x: (vector.x * angle_rad.cos()) - (vector.y * angle_rad.sin()),
|
||||||
|
y: (vector.y * angle_rad.cos()) + (vector.x * angle_rad.sin()),
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -1,9 +1,4 @@
|
|||||||
use raylib::{
|
use raylib::{core::color::Color, math::{Rectangle, Vector2}, prelude::{RaylibDraw, RaylibDrawHandle, RaylibMode2D}, texture::Texture2D};
|
||||||
core::color::Color,
|
|
||||||
math::{Rectangle, Vector2},
|
|
||||||
prelude::{RaylibDraw, RaylibDrawHandle},
|
|
||||||
texture::Texture2D,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A wrapper around an animation spritesheet
|
/// A wrapper around an animation spritesheet
|
||||||
pub struct FrameAnimationWrapper {
|
pub struct FrameAnimationWrapper {
|
||||||
@ -29,7 +24,9 @@ impl FrameAnimationWrapper {
|
|||||||
|
|
||||||
/// Start the animation
|
/// Start the animation
|
||||||
pub fn start(&mut self, handle: &RaylibDrawHandle) {
|
pub fn start(&mut self, handle: &RaylibDrawHandle) {
|
||||||
self.start_time_seconds = handle.get_time();
|
if self.start_time_seconds == 0.0 {
|
||||||
|
self.start_time_seconds = handle.get_time();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop (and reset) the animation
|
/// Stop (and reset) the animation
|
||||||
@ -48,16 +45,17 @@ impl FrameAnimationWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Draw the next frame to the screen at `position`
|
/// Draw the next frame to the screen at `position`
|
||||||
pub fn draw(&mut self, handle: &mut RaylibDrawHandle, position: Vector2) {
|
pub fn draw(&mut self, handle: &mut RaylibMode2D<RaylibDrawHandle>, position: Vector2, rotation: f32) {
|
||||||
let frame_id = self.get_current_frame_id(handle);
|
let frame_id = self.get_current_frame_id(handle);
|
||||||
self.draw_frame(handle, position, frame_id);
|
self.draw_frame(handle, position, rotation, frame_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw a specified frame to the screen at `position`
|
/// Draw a specified frame to the screen at `position`
|
||||||
pub fn draw_frame(
|
pub fn draw_frame(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &mut RaylibDrawHandle,
|
handle: &mut RaylibMode2D<RaylibDrawHandle>,
|
||||||
position: Vector2,
|
position: Vector2,
|
||||||
|
rotation: f32,
|
||||||
frame_number: u32,
|
frame_number: u32,
|
||||||
) {
|
) {
|
||||||
// Determine the col number
|
// Determine the col number
|
||||||
@ -75,8 +73,20 @@ impl FrameAnimationWrapper {
|
|||||||
width: self.size.x,
|
width: self.size.x,
|
||||||
height: self.size.y,
|
height: self.size.y,
|
||||||
};
|
};
|
||||||
|
let frame_dest = Rectangle {
|
||||||
|
x: position.x,
|
||||||
|
y: position.y,
|
||||||
|
width: self.size.x,
|
||||||
|
height: self.size.y,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rotation origin
|
||||||
|
let origin = Vector2 {
|
||||||
|
x: self.size.x / 2.0,
|
||||||
|
y: self.size.y / 2.0
|
||||||
|
};
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
handle.draw_texture_rec(&mut self.sprite_sheet, frame_box, position, Color::WHITE);
|
handle.draw_texture_pro(&mut self.sprite_sheet, frame_box, frame_dest, origin, rotation, Color::WHITE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
32
src/lib/wrappers/complexanimation.rs
Normal file
32
src/lib/wrappers/complexanimation.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
use std::usize;
|
||||||
|
|
||||||
|
use raylib::prelude::*;
|
||||||
|
|
||||||
|
pub struct FrameRange {
|
||||||
|
pub min: usize,
|
||||||
|
pub max: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ComplexAnimationTool {
|
||||||
|
sprite_sheet: Texture2D,
|
||||||
|
frames_per_second: f32,
|
||||||
|
frame_size: Vector2,
|
||||||
|
sprite_sheet_size_frames: Vector2
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComplexAnimationTool {
|
||||||
|
pub fn render_loop(&self, context_2d: &mut RaylibMode2D<RaylibDrawHandle>, bounds: Rectangle, rotation: f32, range: &FrameRange) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_frame(&self, context_2d: &mut RaylibMode2D<RaylibDrawHandle>, bounds: Rectangle, rotation: f32, id: usize) {
|
||||||
|
|
||||||
|
// Convert the ID to an xy
|
||||||
|
let col_id = id % self.sprite_sheet_size_frames.x as usize;
|
||||||
|
let row_id = id / self.sprite_sheet_size_frames.y as usize;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
pub mod audio;
|
pub mod audio;
|
||||||
pub mod animation;
|
pub mod animation;
|
||||||
|
pub mod complexanimation;
|
128
src/logic/gameend.rs
Normal file
128
src/logic/gameend.rs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
use raylib::prelude::*;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
gamecore::{GameCore, GameState},
|
||||||
|
lib::wrappers::audio::player::AudioPlayer,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::screen::Screen;
|
||||||
|
|
||||||
|
const SCREEN_PANEL_SIZE: Vector2 = Vector2 { x: 300.0, y: 300.0 };
|
||||||
|
|
||||||
|
pub struct GameEndScreen {}
|
||||||
|
|
||||||
|
impl GameEndScreen {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Screen for GameEndScreen {
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
draw_handle: &mut RaylibDrawHandle,
|
||||||
|
_thread: &RaylibThread,
|
||||||
|
audio_system: &mut AudioPlayer,
|
||||||
|
game_core: &mut GameCore,
|
||||||
|
) -> Option<GameState> {
|
||||||
|
let mouse_position = draw_handle.get_mouse_position();
|
||||||
|
draw_handle.clear_background(Color::GRAY);
|
||||||
|
// TODO: Maybe we can stick some art here?
|
||||||
|
|
||||||
|
|
||||||
|
// Window dimensions
|
||||||
|
let win_height = draw_handle.get_screen_height();
|
||||||
|
let win_width = draw_handle.get_screen_width();
|
||||||
|
|
||||||
|
// Render the backing to the menu itself
|
||||||
|
draw_handle.draw_rectangle(
|
||||||
|
(win_width / 2) - ((SCREEN_PANEL_SIZE.x as i32 + 6) / 2),
|
||||||
|
(win_height / 2) - ((SCREEN_PANEL_SIZE.y as i32 + 6) / 2),
|
||||||
|
SCREEN_PANEL_SIZE.x as i32 + 6,
|
||||||
|
SCREEN_PANEL_SIZE.y as i32 + 6,
|
||||||
|
Color::BLACK,
|
||||||
|
);
|
||||||
|
draw_handle.draw_rectangle(
|
||||||
|
(win_width / 2) - (SCREEN_PANEL_SIZE.x as i32 / 2),
|
||||||
|
(win_height / 2) - (SCREEN_PANEL_SIZE.y as i32 / 2),
|
||||||
|
SCREEN_PANEL_SIZE.x as i32,
|
||||||
|
SCREEN_PANEL_SIZE.y as i32,
|
||||||
|
Color::WHITE,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render heading text
|
||||||
|
draw_handle.draw_text(
|
||||||
|
"OUT OF BREATH",
|
||||||
|
(win_width / 2) - 80,
|
||||||
|
(win_height / 2) - (SCREEN_PANEL_SIZE.y as i32 / 2) + 10,
|
||||||
|
40,
|
||||||
|
Color::BLACK,
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: Save game progress
|
||||||
|
|
||||||
|
|
||||||
|
// // Close and quit buttons
|
||||||
|
// let bottom_left_button_dimensions = Rectangle {
|
||||||
|
// x: (win_width as f32 / 2.0) - (SCREEN_PANEL_SIZE.x / 2.0) + 5.0,
|
||||||
|
// y: (win_height as f32 / 2.0) + (SCREEN_PANEL_SIZE.y / 2.0) - 50.0,
|
||||||
|
// width: (SCREEN_PANEL_SIZE.x / 2.0) - 15.0,
|
||||||
|
// height: 40.0,
|
||||||
|
// };
|
||||||
|
// let bottom_right_button_dimensions = Rectangle {
|
||||||
|
// x: (win_width as f32 / 2.0) + 5.0,
|
||||||
|
// y: bottom_left_button_dimensions.y,
|
||||||
|
// width: bottom_left_button_dimensions.width,
|
||||||
|
// height: bottom_left_button_dimensions.height,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // Check if the mouse is over either button
|
||||||
|
// let mouse_over_bottom_left_button =
|
||||||
|
// bottom_left_button_dimensions.check_collision_point_rec(mouse_position);
|
||||||
|
// let mouse_over_bottom_right_button =
|
||||||
|
// bottom_right_button_dimensions.check_collision_point_rec(mouse_position);
|
||||||
|
|
||||||
|
// // Render buttons
|
||||||
|
// draw_handle.draw_rectangle_lines_ex(
|
||||||
|
// bottom_left_button_dimensions,
|
||||||
|
// 3,
|
||||||
|
// match mouse_over_bottom_left_button {
|
||||||
|
// true => Color::GRAY,
|
||||||
|
// false => Color::BLACK,
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// draw_handle.draw_text(
|
||||||
|
// "Quit",
|
||||||
|
// bottom_left_button_dimensions.x as i32 + 15,
|
||||||
|
// bottom_left_button_dimensions.y as i32 + 5,
|
||||||
|
// 30,
|
||||||
|
// Color::BLACK,
|
||||||
|
// );
|
||||||
|
// draw_handle.draw_rectangle_lines_ex(
|
||||||
|
// bottom_right_button_dimensions,
|
||||||
|
// 3,
|
||||||
|
// match mouse_over_bottom_right_button {
|
||||||
|
// true => Color::GRAY,
|
||||||
|
// false => Color::BLACK,
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// draw_handle.draw_text(
|
||||||
|
// "Close",
|
||||||
|
// bottom_right_button_dimensions.x as i32 + 15,
|
||||||
|
// bottom_right_button_dimensions.y as i32 + 5,
|
||||||
|
// 30,
|
||||||
|
// Color::BLACK,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// // Handle click actions on the buttons
|
||||||
|
// if draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON) {
|
||||||
|
// if mouse_over_bottom_left_button {
|
||||||
|
// return Some(GameState::GameQuit);
|
||||||
|
// } else if mouse_over_bottom_right_button {
|
||||||
|
// return Some(game_core.last_state);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
101
src/logic/ingame/hud.rs
Normal file
101
src/logic/ingame/hud.rs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
use raylib::prelude::*;
|
||||||
|
|
||||||
|
use crate::{gamecore::GameCore, pallette::TRANSLUCENT_WHITE_96};
|
||||||
|
|
||||||
|
pub fn render_hud(
|
||||||
|
draw_handle: &mut RaylibDrawHandle,
|
||||||
|
game_core: &mut GameCore,
|
||||||
|
window_center: Vector2,
|
||||||
|
) {
|
||||||
|
// Get the relevant data
|
||||||
|
let dist_from_player_to_end = game_core
|
||||||
|
.player
|
||||||
|
.position
|
||||||
|
.distance_to(game_core.world.end_position);
|
||||||
|
let dist_from_start_to_end = Vector2::zero().distance_to(game_core.world.end_position);
|
||||||
|
let progress = ((dist_from_start_to_end - dist_from_player_to_end) / dist_from_start_to_end)
|
||||||
|
.clamp(0.0, 1.0);
|
||||||
|
|
||||||
|
// Determine the progress slider position
|
||||||
|
let slider_bound_height = 20.0;
|
||||||
|
let progress_slider_position = Vector2 {
|
||||||
|
x: window_center.x * 2.0,
|
||||||
|
y: (((window_center.y * 2.0) - (slider_bound_height * 2.0)) * progress)
|
||||||
|
+ slider_bound_height,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render the base of the slider
|
||||||
|
draw_handle.draw_rectangle(
|
||||||
|
(progress_slider_position.x - slider_bound_height) as i32,
|
||||||
|
(progress_slider_position.y - slider_bound_height / 2.0) as i32,
|
||||||
|
slider_bound_height as i32,
|
||||||
|
slider_bound_height as i32,
|
||||||
|
TRANSLUCENT_WHITE_96,
|
||||||
|
);
|
||||||
|
draw_handle.draw_triangle(
|
||||||
|
Vector2 {
|
||||||
|
x: (progress_slider_position.x - slider_bound_height),
|
||||||
|
y: (progress_slider_position.y - slider_bound_height / 2.0),
|
||||||
|
},
|
||||||
|
Vector2 {
|
||||||
|
x: (progress_slider_position.x - slider_bound_height - (slider_bound_height / 2.0)),
|
||||||
|
y: progress_slider_position.y,
|
||||||
|
},
|
||||||
|
Vector2 {
|
||||||
|
x: (progress_slider_position.x - slider_bound_height),
|
||||||
|
y: (progress_slider_position.y + slider_bound_height / 2.0),
|
||||||
|
},
|
||||||
|
TRANSLUCENT_WHITE_96,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render the outline of the slider
|
||||||
|
draw_handle.draw_line_ex(
|
||||||
|
Vector2 {
|
||||||
|
x: (progress_slider_position.x - slider_bound_height),
|
||||||
|
y: (progress_slider_position.y - slider_bound_height / 2.0),
|
||||||
|
},
|
||||||
|
Vector2 {
|
||||||
|
x: progress_slider_position.x,
|
||||||
|
y: (progress_slider_position.y - slider_bound_height / 2.0),
|
||||||
|
},
|
||||||
|
3.0,
|
||||||
|
Color::BLACK,
|
||||||
|
);
|
||||||
|
draw_handle.draw_line_ex(
|
||||||
|
Vector2 {
|
||||||
|
x: (progress_slider_position.x - slider_bound_height),
|
||||||
|
y: (progress_slider_position.y + slider_bound_height / 2.0),
|
||||||
|
},
|
||||||
|
Vector2 {
|
||||||
|
x: progress_slider_position.x,
|
||||||
|
y: (progress_slider_position.y + slider_bound_height / 2.0),
|
||||||
|
},
|
||||||
|
3.0,
|
||||||
|
Color::BLACK,
|
||||||
|
);
|
||||||
|
draw_handle.draw_line_ex(
|
||||||
|
Vector2 {
|
||||||
|
x: (progress_slider_position.x - slider_bound_height),
|
||||||
|
y: (progress_slider_position.y - slider_bound_height / 2.0),
|
||||||
|
},
|
||||||
|
Vector2 {
|
||||||
|
x: (progress_slider_position.x - slider_bound_height - (slider_bound_height / 2.0)),
|
||||||
|
y: progress_slider_position.y,
|
||||||
|
},
|
||||||
|
3.0,
|
||||||
|
Color::BLACK,
|
||||||
|
);
|
||||||
|
draw_handle.draw_line_ex(
|
||||||
|
Vector2 {
|
||||||
|
x: (progress_slider_position.x - slider_bound_height),
|
||||||
|
y: (progress_slider_position.y + slider_bound_height / 2.0),
|
||||||
|
},
|
||||||
|
Vector2 {
|
||||||
|
x: (progress_slider_position.x - slider_bound_height - (slider_bound_height / 2.0)),
|
||||||
|
y: progress_slider_position.y,
|
||||||
|
},
|
||||||
|
3.0,
|
||||||
|
Color::BLACK,
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
@ -1,3 +1,6 @@
|
|||||||
|
mod hud;
|
||||||
|
mod playerlogic;
|
||||||
|
|
||||||
use raylib::prelude::*;
|
use raylib::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -7,11 +10,28 @@ use crate::{
|
|||||||
|
|
||||||
use super::screen::Screen;
|
use super::screen::Screen;
|
||||||
|
|
||||||
pub struct InGameScreen {}
|
pub enum InGameState {
|
||||||
|
BUYING,
|
||||||
|
SWIMMING,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InGameScreen {
|
||||||
|
current_state: InGameState,
|
||||||
|
}
|
||||||
|
|
||||||
impl InGameScreen {
|
impl InGameScreen {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {}
|
Self {
|
||||||
|
current_state: InGameState::SWIMMING,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_world(
|
||||||
|
&mut self,
|
||||||
|
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
|
||||||
|
game_core: &mut GameCore,
|
||||||
|
) {
|
||||||
|
context_2d.draw_circle(0, 0, 10.0, Color::BLACK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,8 +43,54 @@ impl Screen for InGameScreen {
|
|||||||
audio_system: &mut AudioPlayer,
|
audio_system: &mut AudioPlayer,
|
||||||
game_core: &mut GameCore,
|
game_core: &mut GameCore,
|
||||||
) -> Option<GameState> {
|
) -> Option<GameState> {
|
||||||
|
// Calculate DT
|
||||||
|
let dt = draw_handle.get_time() - game_core.last_frame_time;
|
||||||
|
|
||||||
// Clear frame
|
// Clear frame
|
||||||
draw_handle.clear_background(Color::WHITE);
|
draw_handle.clear_background(Color::BLUE);
|
||||||
|
|
||||||
|
// Handle the pause menu being opened
|
||||||
|
if draw_handle.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
|
||||||
|
return Some(GameState::PauseMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Window dimensions
|
||||||
|
let win_height = draw_handle.get_screen_height();
|
||||||
|
let win_width = draw_handle.get_screen_width();
|
||||||
|
let window_center = Vector2 {
|
||||||
|
x: (win_width as f32 / 2.0),
|
||||||
|
y: (win_height as f32 / 2.0),
|
||||||
|
};
|
||||||
|
let camera_window_center = window_center * (1.0 / game_core.master_camera.zoom);
|
||||||
|
|
||||||
|
// Update player movement
|
||||||
|
playerlogic::update_player_movement(draw_handle, game_core, window_center);
|
||||||
|
|
||||||
|
// Open a 2D context
|
||||||
|
{
|
||||||
|
let mut context_2d = draw_handle.begin_mode2D(game_core.master_camera);
|
||||||
|
|
||||||
|
// Render the world
|
||||||
|
self.render_world(&mut context_2d, game_core);
|
||||||
|
|
||||||
|
// Render entities
|
||||||
|
let mut fish = &mut game_core.world.fish;
|
||||||
|
for fish in fish.iter_mut() {
|
||||||
|
fish.update_position(&mut game_core.player, dt);
|
||||||
|
fish.render(&mut context_2d);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render Player
|
||||||
|
playerlogic::render_player(&mut context_2d, game_core);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the hud
|
||||||
|
hud::render_hud(draw_handle, game_core, window_center);
|
||||||
|
|
||||||
|
// Handle player out of breath
|
||||||
|
if game_core.player.breath_percent == 0.0 {
|
||||||
|
return Some(GameState::GameEnd);
|
||||||
|
}
|
||||||
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
206
src/logic/ingame/playerlogic.rs
Normal file
206
src/logic/ingame/playerlogic.rs
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
use raylib::prelude::*;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
gamecore::GameCore,
|
||||||
|
pallette::{TRANSLUCENT_WHITE_128, TRANSLUCENT_WHITE_64, TRANSLUCENT_WHITE_96},
|
||||||
|
};
|
||||||
|
|
||||||
|
const NORMAL_PLAYER_SPEED: i32 = 3;
|
||||||
|
const BOOST_PLAYER_SPEED: i32 = NORMAL_PLAYER_SPEED * 2;
|
||||||
|
const CAMERA_FOLLOW_SPEED: f32 = 0.7;
|
||||||
|
const TURN_SPEED: f32 = 0.15;
|
||||||
|
const BOOST_DECREASE_PER_SECOND: f32 = 0.65;
|
||||||
|
const BOOST_REGEN_PER_SECOND: f32 = 0.25;
|
||||||
|
const BREATH_DECREASE_PER_SECOND: f32 = 0.01;
|
||||||
|
|
||||||
|
pub fn update_player_movement(
|
||||||
|
draw_handle: &mut RaylibDrawHandle,
|
||||||
|
game_core: &mut GameCore,
|
||||||
|
window_center: Vector2,
|
||||||
|
) {
|
||||||
|
// Calculate DT
|
||||||
|
let dt = draw_handle.get_time() - game_core.last_frame_time;
|
||||||
|
|
||||||
|
// Handle player movement
|
||||||
|
let mouse_pose = draw_handle.get_mouse_position();
|
||||||
|
let mouse_world_pose = draw_handle.get_screen_to_world2D(mouse_pose, game_core.master_camera);
|
||||||
|
let raw_movement_direction = mouse_world_pose - game_core.player.position;
|
||||||
|
let mut normalized_movement_direction = raw_movement_direction;
|
||||||
|
normalized_movement_direction.normalize();
|
||||||
|
|
||||||
|
let tau: f32 = PI as f32 * 2.0;
|
||||||
|
// get angles as floats
|
||||||
|
let mut player_angle: f32 = Vector2::zero().angle_to(game_core.player.direction);
|
||||||
|
let mut desired_angle: f32 = Vector2::zero().angle_to(normalized_movement_direction);
|
||||||
|
|
||||||
|
// make angle positive
|
||||||
|
if desired_angle < 0.0 {
|
||||||
|
desired_angle += tau;
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn towards mouse at turn speed
|
||||||
|
if player_angle % tau > desired_angle {
|
||||||
|
if (player_angle % tau) - desired_angle > PI as f32 {
|
||||||
|
player_angle += TURN_SPEED;
|
||||||
|
} else {
|
||||||
|
player_angle -= TURN_SPEED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if desired_angle - (player_angle % tau) > PI as f32 {
|
||||||
|
player_angle -= TURN_SPEED;
|
||||||
|
} else {
|
||||||
|
player_angle += TURN_SPEED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// snap to mouse if close enough
|
||||||
|
if f32::abs(player_angle - desired_angle) < (TURN_SPEED * 1.1) {
|
||||||
|
player_angle = desired_angle;
|
||||||
|
}
|
||||||
|
if player_angle > tau {
|
||||||
|
player_angle -= tau;
|
||||||
|
}
|
||||||
|
if player_angle < 0.0 {
|
||||||
|
player_angle += tau;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set angle
|
||||||
|
game_core.player.direction = Vector2::new(f32::cos(player_angle), f32::sin(player_angle));
|
||||||
|
|
||||||
|
// In the case the player is in "null", just jump the camera to them
|
||||||
|
if game_core.player.position == Vector2::zero() {
|
||||||
|
game_core.master_camera.target = game_core.player.position - (window_center / 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle action buttons
|
||||||
|
let user_request_boost = draw_handle.is_mouse_button_down(MouseButton::MOUSE_LEFT_BUTTON);
|
||||||
|
let user_request_action = draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_RIGHT_BUTTON);
|
||||||
|
|
||||||
|
// Move the player in their direction
|
||||||
|
let speed_multiplier;
|
||||||
|
if user_request_boost && game_core.player.boost_percent >= 0.0 {
|
||||||
|
// Set the speed multiplier
|
||||||
|
speed_multiplier = BOOST_PLAYER_SPEED as f32;
|
||||||
|
|
||||||
|
// Decrease the boost
|
||||||
|
game_core.player.boost_percent -= BOOST_DECREASE_PER_SECOND * dt as f32;
|
||||||
|
game_core.player.is_boosting = true;
|
||||||
|
if game_core.player.boost_percent >= 0.9 {
|
||||||
|
game_core
|
||||||
|
.resources
|
||||||
|
.player_animation_boost_charge
|
||||||
|
.start(draw_handle);
|
||||||
|
game_core.resources.player_animation_regular.stop();
|
||||||
|
game_core.player.is_boost_charging = true;
|
||||||
|
} else {
|
||||||
|
game_core.resources.player_animation_boost_charge.stop();
|
||||||
|
game_core
|
||||||
|
.resources
|
||||||
|
.player_animation_boost
|
||||||
|
.start(draw_handle);
|
||||||
|
game_core.player.is_boost_charging = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Set the speed multiplier
|
||||||
|
speed_multiplier = NORMAL_PLAYER_SPEED as f32;
|
||||||
|
|
||||||
|
// Reset boost animation
|
||||||
|
game_core.player.is_boosting = false;
|
||||||
|
game_core.player.is_boost_charging = false;
|
||||||
|
game_core.resources.player_animation_boost_charge.stop();
|
||||||
|
game_core.resources.player_animation_boost.stop();
|
||||||
|
game_core
|
||||||
|
.resources
|
||||||
|
.player_animation_regular
|
||||||
|
.start(draw_handle);
|
||||||
|
|
||||||
|
// Handle boost regen
|
||||||
|
if !user_request_boost {
|
||||||
|
game_core.player.boost_percent = (game_core.player.boost_percent
|
||||||
|
+ BOOST_REGEN_PER_SECOND * dt as f32)
|
||||||
|
.clamp(0.0, 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the player's breath
|
||||||
|
game_core.player.breath_percent =
|
||||||
|
(game_core.player.breath_percent - BREATH_DECREASE_PER_SECOND * dt as f32).clamp(0.0, 1.0);
|
||||||
|
|
||||||
|
// Only do this if the mouse is far enough away
|
||||||
|
let player_real_movement = game_core.player.direction * speed_multiplier;
|
||||||
|
if raw_movement_direction.distance_to(Vector2::zero()) > game_core.player.size.y / 2.0 {
|
||||||
|
game_core.player.position += player_real_movement;
|
||||||
|
game_core.player.is_moving = true;
|
||||||
|
} else {
|
||||||
|
game_core.player.is_moving = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the camera to follow the player
|
||||||
|
let direction_from_cam_to_player =
|
||||||
|
(game_core.player.position - window_center) - game_core.master_camera.target;
|
||||||
|
let player_screen_position =
|
||||||
|
draw_handle.get_world_to_screen2D(game_core.player.position, game_core.master_camera);
|
||||||
|
|
||||||
|
// Camera only moves if you get close to the edge of the screen
|
||||||
|
if player_screen_position.distance_to(window_center).abs() > (window_center.y - 40.0) {
|
||||||
|
game_core.master_camera.target += player_real_movement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_player(context_2d: &mut RaylibMode2D<RaylibDrawHandle>, game_core: &mut GameCore) {
|
||||||
|
// Get the player
|
||||||
|
let player = &game_core.player;
|
||||||
|
|
||||||
|
// Convert the player direction to a rotation
|
||||||
|
let player_rotation = Vector2::zero().angle_to(player.direction);
|
||||||
|
|
||||||
|
// Render the player's boost ring
|
||||||
|
// This functions both as a breath meter, and as a boost meter
|
||||||
|
let boost_ring_max_radius = player.size.x + 5.0;
|
||||||
|
context_2d.draw_circle(
|
||||||
|
player.position.x as i32,
|
||||||
|
player.position.y as i32,
|
||||||
|
boost_ring_max_radius * player.boost_percent,
|
||||||
|
TRANSLUCENT_WHITE_64,
|
||||||
|
);
|
||||||
|
context_2d.draw_ring(
|
||||||
|
Vector2 {
|
||||||
|
x: player.position.x as i32 as f32,
|
||||||
|
y: player.position.y as i32 as f32,
|
||||||
|
},
|
||||||
|
boost_ring_max_radius,
|
||||||
|
boost_ring_max_radius + 1.0,
|
||||||
|
0,
|
||||||
|
(360.0 * player.breath_percent) as i32,
|
||||||
|
0,
|
||||||
|
TRANSLUCENT_WHITE_96,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render the player based on what is happening
|
||||||
|
if player.is_boost_charging {
|
||||||
|
game_core.resources.player_animation_boost_charge.draw(
|
||||||
|
context_2d,
|
||||||
|
player.position,
|
||||||
|
player_rotation.to_degrees() - 90.0,
|
||||||
|
);
|
||||||
|
} else if player.is_boosting {
|
||||||
|
game_core.resources.player_animation_boost.draw(
|
||||||
|
context_2d,
|
||||||
|
player.position,
|
||||||
|
player_rotation.to_degrees() - 90.0,
|
||||||
|
);
|
||||||
|
} else if player.is_moving {
|
||||||
|
game_core.resources.player_animation_regular.draw(
|
||||||
|
context_2d,
|
||||||
|
player.position,
|
||||||
|
player_rotation.to_degrees() - 90.0,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
game_core.resources.player_animation_regular.draw_frame(
|
||||||
|
context_2d,
|
||||||
|
player.position,
|
||||||
|
player_rotation.to_degrees() - 90.0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -23,13 +23,53 @@ impl Screen for MainMenuScreen {
|
|||||||
audio_system: &mut AudioPlayer,
|
audio_system: &mut AudioPlayer,
|
||||||
game_core: &mut GameCore,
|
game_core: &mut GameCore,
|
||||||
) -> Option<GameState> {
|
) -> Option<GameState> {
|
||||||
|
// Window dimensions
|
||||||
|
let win_height = draw_handle.get_screen_height();
|
||||||
|
let win_width = draw_handle.get_screen_width();
|
||||||
|
|
||||||
// Clear frame
|
// Clear frame
|
||||||
draw_handle.clear_background(Color::WHITE);
|
draw_handle.clear_background(Color::WHITE);
|
||||||
|
|
||||||
// TODO: This is only for testing
|
// Render title
|
||||||
if draw_handle.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
|
draw_handle.draw_text(
|
||||||
return Some(GameState::PauseMenu);
|
"TMP TITLE",
|
||||||
|
(win_height / 2) - 80,
|
||||||
|
win_width / 4,
|
||||||
|
40,
|
||||||
|
Color::BLACK,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Play and quit
|
||||||
|
draw_handle.draw_text(
|
||||||
|
"Play",
|
||||||
|
(win_height / 2) - 80,
|
||||||
|
(win_width / 4) + 100,
|
||||||
|
20,
|
||||||
|
Color::BLACK,
|
||||||
|
);
|
||||||
|
draw_handle.draw_text(
|
||||||
|
"Quit",
|
||||||
|
(win_height / 2) - 80,
|
||||||
|
(win_width / 4) + 140,
|
||||||
|
20,
|
||||||
|
Color::BLACK,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle button presses
|
||||||
|
let mouse_position = draw_handle.get_mouse_position();
|
||||||
|
let mouse_clicked = draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON);
|
||||||
|
|
||||||
|
// Check clicks
|
||||||
|
if mouse_clicked {
|
||||||
|
if mouse_position.y > (win_width as f32 / 4.0) + 100.0
|
||||||
|
&& mouse_position.y < (win_width as f32 / 4.0) + 120.0
|
||||||
|
{
|
||||||
|
return Some(GameState::InGame);
|
||||||
|
} else if mouse_position.y > (win_width as f32 / 4.0) + 140.0
|
||||||
|
&& mouse_position.y < (win_width as f32 / 4.0) + 180.0
|
||||||
|
{
|
||||||
|
return Some(GameState::GameQuit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return None;
|
return None;
|
||||||
|
@ -3,3 +3,4 @@ pub mod loadingscreen;
|
|||||||
pub mod mainmenu;
|
pub mod mainmenu;
|
||||||
pub mod pausemenu;
|
pub mod pausemenu;
|
||||||
pub mod ingame;
|
pub mod ingame;
|
||||||
|
pub mod gameend;
|
41
src/main.rs
41
src/main.rs
@ -2,15 +2,18 @@ mod gamecore;
|
|||||||
mod lib;
|
mod lib;
|
||||||
mod logic;
|
mod logic;
|
||||||
mod resources;
|
mod resources;
|
||||||
|
mod player;
|
||||||
|
mod world;
|
||||||
|
mod pallette;
|
||||||
|
mod entities;
|
||||||
|
mod items;
|
||||||
|
|
||||||
use gamecore::{GameCore, GameState};
|
use gamecore::{GameCore, GameProgress, GameState};
|
||||||
use lib::{utils::profiler::GameProfiler, wrappers::audio::player::AudioPlayer};
|
use lib::{utils::profiler::GameProfiler, wrappers::audio::player::AudioPlayer};
|
||||||
use log::info;
|
use log::info;
|
||||||
use logic::{
|
use logic::{gameend::GameEndScreen, ingame::InGameScreen, loadingscreen::LoadingScreen, mainmenu::MainMenuScreen, pausemenu::PauseMenuScreen, screen::Screen};
|
||||||
loadingscreen::LoadingScreen, mainmenu::MainMenuScreen, pausemenu::PauseMenuScreen,
|
|
||||||
screen::Screen,
|
|
||||||
};
|
|
||||||
use raylib::prelude::*;
|
use raylib::prelude::*;
|
||||||
|
use world::World;
|
||||||
|
|
||||||
// Game Launch Configuration
|
// Game Launch Configuration
|
||||||
const DEFAULT_WINDOW_DIMENSIONS: Vector2 = Vector2 {
|
const DEFAULT_WINDOW_DIMENSIONS: Vector2 = Vector2 {
|
||||||
@ -37,8 +40,14 @@ fn main() {
|
|||||||
// Override the default exit key
|
// Override the default exit key
|
||||||
raylib.set_exit_key(None);
|
raylib.set_exit_key(None);
|
||||||
|
|
||||||
|
// Load the world
|
||||||
|
let world = World::load_from_json("./assets/worlds/mainworld.json".to_string()).expect("Failed to load main world JSON");
|
||||||
|
|
||||||
|
// Load the game progress
|
||||||
|
let game_progress = GameProgress::try_from_file("./assets/savestate.json".to_string());
|
||||||
|
|
||||||
// Set up the game's core state
|
// Set up the game's core state
|
||||||
let mut game_core = GameCore::new(&mut raylib, &raylib_thread);
|
let mut game_core = GameCore::new(&mut raylib, &raylib_thread, world, game_progress);
|
||||||
|
|
||||||
// Set up the game's profiler
|
// Set up the game's profiler
|
||||||
let mut profiler = GameProfiler::new();
|
let mut profiler = GameProfiler::new();
|
||||||
@ -51,6 +60,8 @@ fn main() {
|
|||||||
let mut loading_screen = LoadingScreen::new();
|
let mut loading_screen = LoadingScreen::new();
|
||||||
let mut main_menu_screen = MainMenuScreen::new();
|
let mut main_menu_screen = MainMenuScreen::new();
|
||||||
let mut pause_menu_screen = PauseMenuScreen::new();
|
let mut pause_menu_screen = PauseMenuScreen::new();
|
||||||
|
let mut ingame_screen = InGameScreen::new();
|
||||||
|
let mut game_end_screen = GameEndScreen::new();
|
||||||
|
|
||||||
// Main rendering loop
|
// Main rendering loop
|
||||||
while !raylib.window_should_close() {
|
while !raylib.window_should_close() {
|
||||||
@ -77,6 +88,18 @@ fn main() {
|
|||||||
&mut game_core,
|
&mut game_core,
|
||||||
),
|
),
|
||||||
GameState::GameQuit => None,
|
GameState::GameQuit => None,
|
||||||
|
GameState::InGame => ingame_screen.render(
|
||||||
|
&mut draw_handle,
|
||||||
|
&raylib_thread,
|
||||||
|
&mut audio_system,
|
||||||
|
&mut game_core,
|
||||||
|
),
|
||||||
|
GameState::GameEnd => game_end_screen.render(
|
||||||
|
&mut draw_handle,
|
||||||
|
&raylib_thread,
|
||||||
|
&mut audio_system,
|
||||||
|
&mut game_core,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
// If needed, update the global state
|
// If needed, update the global state
|
||||||
@ -107,6 +130,9 @@ fn main() {
|
|||||||
profiler.data.audio_volume = audio_system.get_master_volume();
|
profiler.data.audio_volume = audio_system.get_master_volume();
|
||||||
profiler.data.active_sounds = audio_system.get_sounds_playing();
|
profiler.data.active_sounds = audio_system.get_sounds_playing();
|
||||||
profiler.data.game_state = game_core.state.to_string();
|
profiler.data.game_state = game_core.state.to_string();
|
||||||
|
profiler.data.player_coins = game_core.player.coins;
|
||||||
|
profiler.data.player_boost_percent = game_core.player.boost_percent;
|
||||||
|
profiler.data.player_breath_percent = game_core.player.breath_percent;
|
||||||
|
|
||||||
// Send telemetry data
|
// Send telemetry data
|
||||||
profiler.update();
|
profiler.update();
|
||||||
@ -132,6 +158,9 @@ fn main() {
|
|||||||
|
|
||||||
// Set the first frame flag
|
// Set the first frame flag
|
||||||
game_core.has_rendered_first_frame = true;
|
game_core.has_rendered_first_frame = true;
|
||||||
|
|
||||||
|
// Update the frame time
|
||||||
|
game_core.last_frame_time = draw_handle.get_time();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
|
22
src/pallette.rs
Normal file
22
src/pallette.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
use raylib::color::Color;
|
||||||
|
|
||||||
|
pub const TRANSLUCENT_WHITE_128: Color = Color {
|
||||||
|
r: 255,
|
||||||
|
g: 255,
|
||||||
|
b: 255,
|
||||||
|
a: 128,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const TRANSLUCENT_WHITE_96: Color = Color {
|
||||||
|
r: 255,
|
||||||
|
g: 255,
|
||||||
|
b: 255,
|
||||||
|
a: 96,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const TRANSLUCENT_WHITE_64: Color = Color {
|
||||||
|
r: 255,
|
||||||
|
g: 255,
|
||||||
|
b: 255,
|
||||||
|
a: 64,
|
||||||
|
};
|
31
src/player.rs
Normal file
31
src/player.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use raylib::math::Vector2;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Player {
|
||||||
|
pub position: Vector2,
|
||||||
|
pub direction: Vector2,
|
||||||
|
pub size: Vector2,
|
||||||
|
pub coins: u32,
|
||||||
|
pub boost_percent: f32,
|
||||||
|
pub breath_percent: f32,
|
||||||
|
pub is_moving: bool,
|
||||||
|
pub is_boosting: bool,
|
||||||
|
pub is_boost_charging: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Player {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
boost_percent: 1.0,
|
||||||
|
size: Vector2 {
|
||||||
|
x: 11.0,
|
||||||
|
y: 21.0
|
||||||
|
},
|
||||||
|
breath_percent: 1.0,
|
||||||
|
..Default::default()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +1,61 @@
|
|||||||
use failure::Error;
|
use failure::Error;
|
||||||
use raylib::{RaylibHandle, RaylibThread, texture::{Image, Texture2D}};
|
use raylib::{
|
||||||
|
math::Vector2,
|
||||||
|
texture::{Image, Texture2D},
|
||||||
|
RaylibHandle, RaylibThread,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::lib::wrappers::animation::FrameAnimationWrapper;
|
||||||
|
|
||||||
/// This struct contains all textures and sounds that must be loaded into (V)RAM at the start of the game
|
/// This struct contains all textures and sounds that must be loaded into (V)RAM at the start of the game
|
||||||
pub struct GlobalResources {
|
pub struct GlobalResources {
|
||||||
|
|
||||||
// Branding
|
// Branding
|
||||||
pub game_logo: Texture2D
|
pub game_logo: Texture2D,
|
||||||
|
|
||||||
|
// Player
|
||||||
|
pub player_animation_regular: FrameAnimationWrapper,
|
||||||
|
pub player_animation_boost_charge: FrameAnimationWrapper,
|
||||||
|
pub player_animation_boost: FrameAnimationWrapper,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalResources {
|
impl GlobalResources {
|
||||||
/// Load all resources. **THIS WILL HANG!**
|
/// Load all resources. **THIS WILL HANG!**
|
||||||
pub fn load_all(raylib: &mut RaylibHandle, thread: &RaylibThread) -> Result<GlobalResources, String> {
|
pub fn load_all(
|
||||||
|
raylib: &mut RaylibHandle,
|
||||||
|
thread: &RaylibThread,
|
||||||
|
) -> Result<GlobalResources, String> {
|
||||||
Ok(GlobalResources {
|
Ok(GlobalResources {
|
||||||
game_logo: raylib.load_texture_from_image(&thread, &Image::load_image("./assets/img/logos/game-logo.png")?)?
|
game_logo: raylib.load_texture_from_image(
|
||||||
|
&thread,
|
||||||
|
&Image::load_image("./assets/img/logos/game-logo.png")?,
|
||||||
|
)?,
|
||||||
|
player_animation_regular: FrameAnimationWrapper::new(
|
||||||
|
raylib.load_texture_from_image(
|
||||||
|
&thread,
|
||||||
|
&Image::load_image("./assets/img/character/diveNormal.png")?,
|
||||||
|
)?,
|
||||||
|
Vector2 { x: 11.0, y: 21.0 },
|
||||||
|
8,
|
||||||
|
100 / 8,
|
||||||
|
),
|
||||||
|
player_animation_boost_charge: FrameAnimationWrapper::new(
|
||||||
|
raylib.load_texture_from_image(
|
||||||
|
&thread,
|
||||||
|
&Image::load_image("./assets/img/character/diveStrokeCharge.png")?,
|
||||||
|
)?,
|
||||||
|
Vector2 { x: 11.0, y: 21.0 },
|
||||||
|
21,
|
||||||
|
100 / 4,
|
||||||
|
),
|
||||||
|
player_animation_boost: FrameAnimationWrapper::new(
|
||||||
|
raylib.load_texture_from_image(
|
||||||
|
&thread,
|
||||||
|
&Image::load_image("./assets/img/character/diveStroke.png")?,
|
||||||
|
)?,
|
||||||
|
Vector2 { x: 17.0, y: 21.0 },
|
||||||
|
21,
|
||||||
|
30,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
47
src/world.rs
Normal file
47
src/world.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
use std::{fs::File, io::BufReader};
|
||||||
|
|
||||||
|
use raylib::math::Vector2;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::io::Read;
|
||||||
|
use failure::Error;
|
||||||
|
|
||||||
|
use crate::entities::fish::FishEntity;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct World {
|
||||||
|
pub end_position: Vector2,
|
||||||
|
|
||||||
|
#[serde(rename = "fish")]
|
||||||
|
pub fish_positions: Vec<Vector2>,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub fish: Vec<FishEntity>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl World {
|
||||||
|
pub fn load_from_json(file: String) -> Result<Self, Error> {
|
||||||
|
// Load the file
|
||||||
|
let file = File::open(file)?;
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
let mut result: World = serde_json::from_reader(reader)?;
|
||||||
|
|
||||||
|
// Init all fish
|
||||||
|
result.fish = FishEntity::new_from_positions(&result.fish_positions);
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spend_coins(&mut self, count: usize) {
|
||||||
|
for _ in 0..count {
|
||||||
|
self.fish.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
for fish in self.fish.iter_mut() {
|
||||||
|
fish.following_player = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user