Merge branch 'master' into assets
This commit is contained in:
commit
9035b1d872
50
assets/img/character/stunned.json
Normal file
50
assets/img/character/stunned.json
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{ "frames": {
|
||||||
|
"walk1 0.png": {
|
||||||
|
"frame": { "x": 0, "y": 0, "w": 12, "h": 22 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 22 },
|
||||||
|
"sourceSize": { "w": 12, "h": 22 },
|
||||||
|
"duration": 200
|
||||||
|
},
|
||||||
|
"walk1 1.png": {
|
||||||
|
"frame": { "x": 12, "y": 0, "w": 12, "h": 22 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 22 },
|
||||||
|
"sourceSize": { "w": 12, "h": 22 },
|
||||||
|
"duration": 200
|
||||||
|
},
|
||||||
|
"walk1 2.png": {
|
||||||
|
"frame": { "x": 24, "y": 0, "w": 12, "h": 22 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 22 },
|
||||||
|
"sourceSize": { "w": 12, "h": 22 },
|
||||||
|
"duration": 200
|
||||||
|
},
|
||||||
|
"walk1 3.png": {
|
||||||
|
"frame": { "x": 36, "y": 0, "w": 12, "h": 22 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 22 },
|
||||||
|
"sourceSize": { "w": 12, "h": 22 },
|
||||||
|
"duration": 200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"app": "http://www.aseprite.org/",
|
||||||
|
"version": "1.2.27-x64",
|
||||||
|
"image": "stunned.png",
|
||||||
|
"format": "RGBA8888",
|
||||||
|
"size": { "w": 48, "h": 22 },
|
||||||
|
"scale": "1",
|
||||||
|
"frameTags": [
|
||||||
|
],
|
||||||
|
"layers": [
|
||||||
|
{ "name": "Layer", "opacity": 255, "blendMode": "normal" }
|
||||||
|
],
|
||||||
|
"slices": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
BIN
assets/img/character/stunned.png
Normal file
BIN
assets/img/character/stunned.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 841 B |
BIN
assets/img/map/darkness.png
Normal file
BIN
assets/img/map/darkness.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 50 KiB |
File diff suppressed because one or more lines are too long
15
src/entities/enemy/base.rs
Normal file
15
src/entities/enemy/base.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use raylib::prelude::*;
|
||||||
|
|
||||||
|
use crate::{player::Player, resources::GlobalResources};
|
||||||
|
|
||||||
|
pub trait EnemyBase {
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
|
||||||
|
player: &mut Player,
|
||||||
|
resources: &mut GlobalResources,
|
||||||
|
dt: f64,
|
||||||
|
);
|
||||||
|
fn handle_logic(&mut self, player: &mut Player, dt: f64);
|
||||||
|
fn handle_getting_attacked(&mut self, stun_duration: f64, current_time: f64);
|
||||||
|
}
|
93
src/entities/enemy/jellyfish.rs
Normal file
93
src/entities/enemy/jellyfish.rs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
use super::base::EnemyBase;
|
||||||
|
use crate::{
|
||||||
|
lib::utils::calculate_linear_slide, pallette::TRANSLUCENT_RED_64, player::Player,
|
||||||
|
resources::GlobalResources,
|
||||||
|
};
|
||||||
|
use raylib::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
const JELLYFISH_STUN_DURATION: f64 = 0.75;
|
||||||
|
const JELLYFISH_STUN_REACH: f32 = 20.0;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||||
|
pub struct JellyFish {
|
||||||
|
pub position: Vector2,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub stunned_timer: f64,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub max_stunned_time: f64,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub do_stun_player: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JellyFish {}
|
||||||
|
|
||||||
|
impl EnemyBase for JellyFish {
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
context_2d: &mut raylib::prelude::RaylibMode2D<raylib::prelude::RaylibDrawHandle>,
|
||||||
|
player: &mut Player,
|
||||||
|
resources: &mut GlobalResources,
|
||||||
|
dt: f64,
|
||||||
|
) {
|
||||||
|
let is_jelly_stunned = self.stunned_timer > 0.0;
|
||||||
|
|
||||||
|
// Simple sine position
|
||||||
|
let v_trans = if is_jelly_stunned {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
context_2d.get_time().sin()
|
||||||
|
};
|
||||||
|
let trans_pose = Vector2 {
|
||||||
|
x: self.position.x,
|
||||||
|
y: self.position.y + (2.0 * v_trans as f32),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render the stun ring
|
||||||
|
if self.max_stunned_time > 0.0 && self.stunned_timer > 0.0 {
|
||||||
|
let stun_ring_alpha =
|
||||||
|
calculate_linear_slide(self.stunned_timer / self.max_stunned_time);
|
||||||
|
context_2d.draw_circle_v(
|
||||||
|
trans_pose,
|
||||||
|
JELLYFISH_STUN_REACH,
|
||||||
|
TRANSLUCENT_RED_64.fade(0.55 * stun_ring_alpha as f32),
|
||||||
|
);
|
||||||
|
self.stunned_timer -= dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the jellyfish
|
||||||
|
resources
|
||||||
|
.jellyfish_animation_regular
|
||||||
|
.draw(context_2d, trans_pose, 0.0);
|
||||||
|
|
||||||
|
// Only do stun loop if not stunned
|
||||||
|
if !is_jelly_stunned {
|
||||||
|
resources
|
||||||
|
.jellyfish_animation_attack
|
||||||
|
.draw(context_2d, trans_pose, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the jelly is in stun mode
|
||||||
|
self.do_stun_player = (resources
|
||||||
|
.jellyfish_animation_attack
|
||||||
|
.get_current_frame_id(context_2d)
|
||||||
|
== 13)
|
||||||
|
&& !is_jelly_stunned;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_logic(&mut self, player: &mut Player, dt: f64) {
|
||||||
|
// Handle stunning the player
|
||||||
|
if self.do_stun_player {
|
||||||
|
if self.position.distance_to(player.position).abs() <= JELLYFISH_STUN_REACH {
|
||||||
|
player.set_stun_seconds(JELLYFISH_STUN_DURATION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_getting_attacked(&mut self, stun_duration: f64, current_time: f64) {
|
||||||
|
self.stunned_timer = stun_duration;
|
||||||
|
self.max_stunned_time = stun_duration;
|
||||||
|
}
|
||||||
|
}
|
3
src/entities/enemy/mod.rs
Normal file
3
src/entities/enemy/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod base;
|
||||||
|
pub mod jellyfish;
|
||||||
|
pub mod octopus;
|
131
src/entities/enemy/octopus.rs
Normal file
131
src/entities/enemy/octopus.rs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
use crate::{lib::utils::calculate_linear_slide, pallette::{TRANSLUCENT_RED_64, TRANSLUCENT_WHITE_128, TRANSLUCENT_WHITE_64}, player::Player};
|
||||||
|
|
||||||
|
use super::base::EnemyBase;
|
||||||
|
use rand::{prelude::ThreadRng, Rng};
|
||||||
|
use raylib::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
const OCTOPUS_SUCK_AIR_DELAY: f64 = 3.5;
|
||||||
|
const OCTOPUS_SUCK_AIR_RANGE: f32 = 70.0;
|
||||||
|
const OCTOPUS_SUCK_AIR_DURATION: f64 = 1.0;
|
||||||
|
const OCTOPUS_SUCK_AIR_AMOUNT: f32 = 0.1;
|
||||||
|
// const RNG: ThreadRng = rand::thread_rng();
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||||
|
struct OctopusAirBubble {
|
||||||
|
position: Vector2,
|
||||||
|
speed: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||||
|
pub struct Octopus {
|
||||||
|
pub position_a: Vector2,
|
||||||
|
pub position_b: Vector2,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub current_position: Vector2,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub stunned_timer: f64,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub max_stunned_time: f64,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub suck_air_time_remaining: f64,
|
||||||
|
#[serde(skip)]
|
||||||
|
suck_air_bubbles: Vec<OctopusAirBubble>,
|
||||||
|
#[serde(skip)]
|
||||||
|
has_taken_air_from_player: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Octopus {}
|
||||||
|
|
||||||
|
impl EnemyBase for Octopus {
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
context_2d: &mut raylib::prelude::RaylibMode2D<raylib::prelude::RaylibDrawHandle>,
|
||||||
|
player: &mut Player,
|
||||||
|
resources: &mut crate::resources::GlobalResources,
|
||||||
|
dt: f64,
|
||||||
|
) {
|
||||||
|
let is_octopus_stunned = self.stunned_timer > 0.0;
|
||||||
|
|
||||||
|
// Simple sine position
|
||||||
|
let h_trans = (context_2d.get_time() / 8.0).sin().abs() as f32;
|
||||||
|
|
||||||
|
// Modify the current pose
|
||||||
|
let dist_a_to_b = self.position_b - self.position_a;
|
||||||
|
self.current_position = (dist_a_to_b * h_trans) + self.position_a;
|
||||||
|
|
||||||
|
// Render the stun ring
|
||||||
|
if self.max_stunned_time > 0.0 && self.stunned_timer > 0.0 {
|
||||||
|
let stun_ring_alpha =
|
||||||
|
calculate_linear_slide(self.stunned_timer / self.max_stunned_time);
|
||||||
|
context_2d.draw_circle_v(
|
||||||
|
self.current_position,
|
||||||
|
20.0,
|
||||||
|
TRANSLUCENT_RED_64.fade(0.55 * stun_ring_alpha as f32),
|
||||||
|
);
|
||||||
|
self.stunned_timer -= dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every once in a while, start sucking air
|
||||||
|
if (context_2d.get_time() % OCTOPUS_SUCK_AIR_DELAY) < 0.1
|
||||||
|
&& self.suck_air_time_remaining == 0.0 && !is_octopus_stunned
|
||||||
|
{
|
||||||
|
self.suck_air_time_remaining = OCTOPUS_SUCK_AIR_DURATION;
|
||||||
|
self.has_taken_air_from_player = false;
|
||||||
|
|
||||||
|
// Spawn a few air bubbles if the player is in range
|
||||||
|
if player.position.distance_to(self.current_position).abs() <= OCTOPUS_SUCK_AIR_RANGE {
|
||||||
|
for _ in 0..5 {
|
||||||
|
self.suck_air_bubbles.push(OctopusAirBubble {
|
||||||
|
position: player.position,
|
||||||
|
speed: rand::thread_rng().gen_range(0.8..1.3),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle sucking air bubble animation
|
||||||
|
if self.suck_air_time_remaining > 0.0 {
|
||||||
|
// Render and update all bubbles
|
||||||
|
for bubble in self.suck_air_bubbles.iter_mut() {
|
||||||
|
// Get the direction from the bubble to the octopus
|
||||||
|
let direction = (self.current_position - bubble.position).normalized();
|
||||||
|
|
||||||
|
// Render the bubble
|
||||||
|
context_2d.draw_circle_v(bubble.position, 2.0, TRANSLUCENT_WHITE_128);
|
||||||
|
|
||||||
|
// Move the bubble
|
||||||
|
bubble.position += direction * bubble.speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce time
|
||||||
|
self.suck_air_time_remaining = (self.suck_air_time_remaining - dt).max(0.0);
|
||||||
|
} else {
|
||||||
|
self.suck_air_bubbles.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: TMP
|
||||||
|
context_2d.draw_circle_v(self.current_position, 10.0, Color::RED);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_logic(&mut self, player: &mut crate::player::Player, dt: f64) {
|
||||||
|
if self.suck_air_time_remaining > 0.0 && !self.has_taken_air_from_player {
|
||||||
|
if player.position.distance_to(self.current_position).abs() <= OCTOPUS_SUCK_AIR_RANGE {
|
||||||
|
// Take air from the player
|
||||||
|
println!("Stealing");
|
||||||
|
player.breath_percent -= OCTOPUS_SUCK_AIR_AMOUNT;
|
||||||
|
|
||||||
|
// Set the flag
|
||||||
|
self.has_taken_air_from_player = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_getting_attacked(&mut self, stun_duration: f64, current_time: f64) {
|
||||||
|
self.stunned_timer = stun_duration;
|
||||||
|
self.max_stunned_time = stun_duration;
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1,2 @@
|
|||||||
pub mod fish;
|
pub mod fish;
|
||||||
|
pub mod enemy;
|
@ -6,7 +6,11 @@ use raylib::{
|
|||||||
camera::Camera2D, math::Vector2, prelude::RaylibDrawHandle, RaylibHandle, RaylibThread,
|
camera::Camera2D, math::Vector2, prelude::RaylibDrawHandle, RaylibHandle, RaylibThread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{items::ShopItems, player::Player, resources::GlobalResources, world::World};
|
use crate::{
|
||||||
|
player::{Player, PlayerInventory},
|
||||||
|
resources::GlobalResources,
|
||||||
|
world::World,
|
||||||
|
};
|
||||||
|
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
@ -31,10 +35,10 @@ impl fmt::Display for GameState {
|
|||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||||
pub struct GameProgress {
|
pub struct GameProgress {
|
||||||
coins: u32,
|
pub coins: u32,
|
||||||
max_depth: f32,
|
pub max_depth: f32,
|
||||||
fastest_time: Option<f64>,
|
pub fastest_time: Option<f64>,
|
||||||
inventory: Vec<ShopItems>,
|
pub inventory: PlayerInventory,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameProgress {
|
impl GameProgress {
|
||||||
|
62
src/items.rs
62
src/items.rs
@ -1,10 +1,56 @@
|
|||||||
use serde::{Serialize, Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
#[serde(tag = "t", content = "c")]
|
pub struct StunGun {
|
||||||
pub enum ShopItems {
|
pub range: f32,
|
||||||
StunGun(u8),
|
pub duration: f64,
|
||||||
AirBag,
|
}
|
||||||
Flashlight(u8),
|
|
||||||
Flippers(u8)
|
impl StunGun {
|
||||||
}
|
pub fn lvl1() -> Self {
|
||||||
|
Self {
|
||||||
|
range: 30.0,
|
||||||
|
duration: 0.75,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn lvl2() -> Self {
|
||||||
|
Self {
|
||||||
|
range: 60.0,
|
||||||
|
duration: 1.25,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AirBag;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Flashlight {
|
||||||
|
pub radius: f32
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Flashlight {
|
||||||
|
pub fn lvl1() -> Self {
|
||||||
|
Self {
|
||||||
|
radius: 0.25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Flippers {
|
||||||
|
pub speed_increase: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Flippers {
|
||||||
|
pub fn lvl1() -> Self {
|
||||||
|
Self {
|
||||||
|
speed_increase: 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn lvl2() -> Self {
|
||||||
|
Self {
|
||||||
|
speed_increase: 1.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,2 +1,12 @@
|
|||||||
pub mod profiler;
|
pub mod profiler;
|
||||||
pub mod triangles;
|
pub mod triangles;
|
||||||
|
|
||||||
|
pub fn calculate_linear_slide(playthrough_percent: f64) -> f64 {
|
||||||
|
if playthrough_percent < 0.25 {
|
||||||
|
return playthrough_percent / 0.25;
|
||||||
|
} else if playthrough_percent > 0.75 {
|
||||||
|
return 1.0 - ((playthrough_percent - 0.75) / 0.25);
|
||||||
|
} else {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
use raylib::math::Vector2;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use serialstudio::{
|
use serialstudio::{
|
||||||
data::{DataGroup, DataSet, TelemetryFrame},
|
data::{DataGroup, DataSet, TelemetryFrame},
|
||||||
@ -21,7 +22,8 @@ pub struct ProfilerData {
|
|||||||
// Player
|
// Player
|
||||||
pub player_coins: u32,
|
pub player_coins: u32,
|
||||||
pub player_boost_percent: f32,
|
pub player_boost_percent: f32,
|
||||||
pub player_breath_percent: f32
|
pub player_breath_percent: f32,
|
||||||
|
pub player_pose: Vector2
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The development profiler
|
/// The development profiler
|
||||||
@ -147,6 +149,20 @@ impl GameProfiler {
|
|||||||
unit: Some("%".to_string()),
|
unit: Some("%".to_string()),
|
||||||
w_type: None,
|
w_type: None,
|
||||||
},
|
},
|
||||||
|
DataSet {
|
||||||
|
title: Some("X".to_string()),
|
||||||
|
value: json!(self.data.player_pose.x),
|
||||||
|
graph: Some(false),
|
||||||
|
unit: Some("pixels".to_string()),
|
||||||
|
w_type: None,
|
||||||
|
},
|
||||||
|
DataSet {
|
||||||
|
title: Some("Y".to_string()),
|
||||||
|
value: json!(self.data.player_pose.y),
|
||||||
|
graph: Some(false),
|
||||||
|
unit: Some("pixels".to_string()),
|
||||||
|
w_type: None,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -2,7 +2,7 @@ use raylib::{core::color::Color, math::{Rectangle, Vector2}, prelude::{RaylibDra
|
|||||||
|
|
||||||
/// A wrapper around an animation spritesheet
|
/// A wrapper around an animation spritesheet
|
||||||
pub struct FrameAnimationWrapper {
|
pub struct FrameAnimationWrapper {
|
||||||
sprite_sheet: Texture2D,
|
pub sprite_sheet: Texture2D,
|
||||||
size: Vector2,
|
size: Vector2,
|
||||||
frame_count: u32,
|
frame_count: u32,
|
||||||
frames_per_second: u8,
|
frames_per_second: u8,
|
||||||
|
@ -8,13 +8,7 @@ pub fn render_hud(
|
|||||||
window_center: Vector2,
|
window_center: Vector2,
|
||||||
) {
|
) {
|
||||||
// Get the relevant data
|
// Get the relevant data
|
||||||
let dist_from_player_to_end = game_core
|
let progress = game_core.player.calculate_depth_percent(&game_core.world);
|
||||||
.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
|
// Determine the progress slider position
|
||||||
let slider_bound_height = 20.0;
|
let slider_bound_height = 20.0;
|
||||||
|
@ -4,9 +4,10 @@ mod playerlogic;
|
|||||||
use raylib::prelude::*;
|
use raylib::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
entities::enemy::base::EnemyBase,
|
||||||
gamecore::{GameCore, GameState},
|
gamecore::{GameCore, GameState},
|
||||||
lib::wrappers::audio::player::AudioPlayer,
|
lib::wrappers::audio::player::AudioPlayer,
|
||||||
pallette::{SKY, WATER},
|
pallette::{SKY, WATER, WATER_DARK},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::screen::Screen;
|
use super::screen::Screen;
|
||||||
@ -31,6 +32,7 @@ impl InGameScreen {
|
|||||||
&mut self,
|
&mut self,
|
||||||
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
|
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
|
||||||
game_core: &mut GameCore,
|
game_core: &mut GameCore,
|
||||||
|
dt: f64,
|
||||||
) {
|
) {
|
||||||
// Build source bounds
|
// Build source bounds
|
||||||
let source_bounds = Rectangle {
|
let source_bounds = Rectangle {
|
||||||
@ -47,7 +49,21 @@ impl InGameScreen {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Clear the background
|
// Clear the background
|
||||||
context_2d.draw_rectangle_rec(world_bounds, WATER);
|
context_2d.draw_rectangle_gradient_v(
|
||||||
|
world_bounds.x as i32,
|
||||||
|
world_bounds.y as i32,
|
||||||
|
world_bounds.width as i32,
|
||||||
|
world_bounds.height as i32,
|
||||||
|
WATER,
|
||||||
|
WATER_DARK,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render fish
|
||||||
|
let fish_clone = game_core.world.fish.clone();
|
||||||
|
for fish in game_core.world.fish.iter_mut() {
|
||||||
|
fish.update_position(&mut game_core.player, dt, &fish_clone);
|
||||||
|
fish.render(context_2d);
|
||||||
|
}
|
||||||
|
|
||||||
// Render the world texture
|
// Render the world texture
|
||||||
context_2d.draw_texture_rec(
|
context_2d.draw_texture_rec(
|
||||||
@ -68,13 +84,63 @@ impl InGameScreen {
|
|||||||
) {
|
) {
|
||||||
// Render every collider
|
// Render every collider
|
||||||
for collider in game_core.world.colliders.iter() {
|
for collider in game_core.world.colliders.iter() {
|
||||||
context_2d.draw_rectangle_lines_ex(
|
context_2d.draw_rectangle_lines_ex(collider, 1, Color::RED);
|
||||||
collider,
|
|
||||||
1,
|
|
||||||
Color::RED,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_darkness(&mut self, draw_handle: &mut RaylibDrawHandle, game_core: &mut GameCore) {
|
||||||
|
// Calculate the min view radius based on the current flashlight
|
||||||
|
let mut min_radius = 0.0;
|
||||||
|
if game_core.player.inventory.flashlight.is_some() {
|
||||||
|
min_radius = game_core
|
||||||
|
.player
|
||||||
|
.inventory
|
||||||
|
.flashlight
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the window center
|
||||||
|
let win_height = draw_handle.get_screen_height();
|
||||||
|
let win_width = draw_handle.get_screen_width();
|
||||||
|
|
||||||
|
// Calculate the occusion radius based on depth
|
||||||
|
let radius = (1.0
|
||||||
|
- (game_core.player.calculate_depth_percent(&game_core.world) * 1.3).clamp(0.0, 1.0))
|
||||||
|
.max(min_radius);
|
||||||
|
|
||||||
|
// Determine width and height scales
|
||||||
|
// This is clamped to make the rendering logic below easier by removing the need to overdraw
|
||||||
|
let width_scale = (5.0 * radius).max(0.5);
|
||||||
|
let height_scale = (5.0 * radius).max(0.5);
|
||||||
|
|
||||||
|
// Get the base sizes of everything
|
||||||
|
let texture_width = game_core.resources.darkness_overlay.width as f32;
|
||||||
|
let texture_height = game_core.resources.darkness_overlay.height as f32;
|
||||||
|
let texture_width_scaled = texture_width * width_scale;
|
||||||
|
let texture_height_scaled = texture_height * height_scale;
|
||||||
|
|
||||||
|
// Render the overlay
|
||||||
|
draw_handle.draw_texture_pro(
|
||||||
|
&game_core.resources.darkness_overlay,
|
||||||
|
Rectangle {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
width: texture_width,
|
||||||
|
height: texture_height,
|
||||||
|
},
|
||||||
|
Rectangle {
|
||||||
|
x: (win_width as f32 - texture_width_scaled) / 2.0,
|
||||||
|
y: (win_height as f32 - texture_height_scaled) / 2.0,
|
||||||
|
width: texture_width_scaled,
|
||||||
|
height: texture_height_scaled,
|
||||||
|
},
|
||||||
|
Vector2 { x: 0.0, y: 0.0 },
|
||||||
|
0.0,
|
||||||
|
Color::WHITE,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Screen for InGameScreen {
|
impl Screen for InGameScreen {
|
||||||
@ -113,22 +179,40 @@ impl Screen for InGameScreen {
|
|||||||
let mut context_2d = draw_handle.begin_mode2D(game_core.master_camera);
|
let mut context_2d = draw_handle.begin_mode2D(game_core.master_camera);
|
||||||
|
|
||||||
// Render the world
|
// Render the world
|
||||||
self.render_world(&mut context_2d, game_core);
|
self.render_world(&mut context_2d, game_core, dt);
|
||||||
if game_core.show_simple_debug_info{
|
if game_core.show_simple_debug_info {
|
||||||
self.render_colliders(&mut context_2d, game_core);
|
self.render_colliders(&mut context_2d, game_core);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render entities
|
// Render entities
|
||||||
let fish_clone = game_core.world.fish.clone();
|
for jellyfish in game_core.world.jellyfish.iter_mut() {
|
||||||
for fish in game_core.world.fish.iter_mut() {
|
jellyfish.handle_logic(&mut game_core.player, dt);
|
||||||
fish.update_position(&mut game_core.player, dt, &fish_clone);
|
jellyfish.render(
|
||||||
fish.render(&mut context_2d);
|
&mut context_2d,
|
||||||
|
&mut game_core.player,
|
||||||
|
&mut game_core.resources,
|
||||||
|
dt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for octopus in game_core.world.octopus.iter_mut() {
|
||||||
|
octopus.handle_logic(&mut game_core.player, dt);
|
||||||
|
octopus.render(
|
||||||
|
&mut context_2d,
|
||||||
|
&mut game_core.player,
|
||||||
|
&mut game_core.resources,
|
||||||
|
dt,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render Player
|
// Render Player
|
||||||
playerlogic::render_player(&mut context_2d, game_core);
|
game_core
|
||||||
|
.player
|
||||||
|
.render(&mut context_2d, &mut game_core.resources, dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render the darkness layer
|
||||||
|
self.render_darkness(draw_handle, game_core);
|
||||||
|
|
||||||
// Render the hud
|
// Render the hud
|
||||||
hud::render_hud(draw_handle, game_core, window_center);
|
hud::render_hud(draw_handle, game_core, window_center);
|
||||||
|
|
||||||
|
@ -5,13 +5,13 @@ use crate::{
|
|||||||
pallette::{TRANSLUCENT_WHITE_128, TRANSLUCENT_WHITE_64, TRANSLUCENT_WHITE_96},
|
pallette::{TRANSLUCENT_WHITE_128, TRANSLUCENT_WHITE_64, TRANSLUCENT_WHITE_96},
|
||||||
};
|
};
|
||||||
|
|
||||||
const NORMAL_PLAYER_SPEED: i32 = 3;
|
const NORMAL_PLAYER_SPEED: i32 = 1;
|
||||||
const BOOST_PLAYER_SPEED: i32 = NORMAL_PLAYER_SPEED * 2;
|
const BOOST_PLAYER_SPEED: i32 = NORMAL_PLAYER_SPEED * 2;
|
||||||
const CAMERA_FOLLOW_SPEED: f32 = 0.7;
|
const CAMERA_FOLLOW_SPEED: f32 = 0.7;
|
||||||
const TURN_SPEED: f32 = 0.15;
|
const TURN_SPEED: f32 = 0.15;
|
||||||
const BOOST_DECREASE_PER_SECOND: f32 = 0.65;
|
const BOOST_DECREASE_PER_SECOND: f32 = 0.65;
|
||||||
const BOOST_REGEN_PER_SECOND: f32 = 0.25;
|
const BOOST_REGEN_PER_SECOND: f32 = 0.25;
|
||||||
const BREATH_DECREASE_PER_SECOND: f32 = 0.01;
|
const BREATH_DECREASE_PER_SECOND: f32 = 0.02;
|
||||||
|
|
||||||
pub fn update_player_movement(
|
pub fn update_player_movement(
|
||||||
draw_handle: &mut RaylibDrawHandle,
|
draw_handle: &mut RaylibDrawHandle,
|
||||||
@ -65,7 +65,9 @@ pub fn update_player_movement(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set angle
|
// set angle
|
||||||
game_core.player.direction = Vector2::new(f32::cos(player_angle), f32::sin(player_angle));
|
if !game_core.player.is_stunned() {
|
||||||
|
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
|
// In the case the player is in "null", just jump the camera to them
|
||||||
if game_core.player.position == Vector2::zero() {
|
if game_core.player.position == Vector2::zero() {
|
||||||
@ -76,8 +78,12 @@ pub fn update_player_movement(
|
|||||||
let user_request_boost = draw_handle.is_mouse_button_down(MouseButton::MOUSE_LEFT_BUTTON);
|
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);
|
let user_request_action = draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_RIGHT_BUTTON);
|
||||||
|
|
||||||
|
if user_request_action {
|
||||||
|
game_core.player.begin_attack(&mut game_core.world, draw_handle.get_time());
|
||||||
|
}
|
||||||
|
|
||||||
// Move the player in their direction
|
// Move the player in their direction
|
||||||
let speed_multiplier;
|
let mut speed_multiplier;
|
||||||
if user_request_boost && game_core.player.boost_percent >= 0.0 {
|
if user_request_boost && game_core.player.boost_percent >= 0.0 {
|
||||||
// Set the speed multiplier
|
// Set the speed multiplier
|
||||||
speed_multiplier = BOOST_PLAYER_SPEED as f32;
|
speed_multiplier = BOOST_PLAYER_SPEED as f32;
|
||||||
@ -122,29 +128,58 @@ pub fn update_player_movement(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle flippers doing a speed increase
|
||||||
|
if game_core.player.inventory.flippers.is_some() {
|
||||||
|
speed_multiplier = speed_multiplier
|
||||||
|
* game_core
|
||||||
|
.player
|
||||||
|
.inventory
|
||||||
|
.flippers
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.speed_increase;
|
||||||
|
}
|
||||||
|
|
||||||
// Update the player's breath
|
// Update the player's breath
|
||||||
game_core.player.breath_percent =
|
game_core.player.breath_percent =
|
||||||
(game_core.player.breath_percent - BREATH_DECREASE_PER_SECOND * dt as f32).clamp(0.0, 1.0);
|
(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
|
// Only do this if the mouse is far enough away
|
||||||
let player_real_movement = game_core.player.direction * speed_multiplier;
|
let player_stunned = game_core.player.stun_timer > 0.0;
|
||||||
if raw_movement_direction.distance_to(Vector2::zero()) > game_core.player.size.y / 2.0 {
|
let mut player_real_movement = game_core.player.direction * speed_multiplier;
|
||||||
game_core.player.is_moving = true;
|
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_stunned()
|
||||||
|
{
|
||||||
|
if game_core.player.is_moving {
|
||||||
|
// move in x
|
||||||
|
game_core.player.position.x += player_real_movement.x;
|
||||||
|
|
||||||
// Check for any collisions
|
// Check for any collisions
|
||||||
for collider in game_core.world.colliders.iter() {
|
for collider in game_core.world.colliders.iter() {
|
||||||
if game_core.player.collides_with_rec(collider) {
|
if game_core.player.collides_with_rec(collider) {
|
||||||
game_core.player.is_moving = false;
|
game_core.player.position.x -= player_real_movement.x;
|
||||||
break;
|
player_real_movement.x = 0.0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// move in y
|
||||||
|
game_core.player.position.y += player_real_movement.y;
|
||||||
|
|
||||||
|
// Check for any collisions
|
||||||
|
for collider in game_core.world.colliders.iter() {
|
||||||
|
if game_core.player.collides_with_rec(collider) {
|
||||||
|
game_core.player.position.y -= player_real_movement.y;
|
||||||
|
player_real_movement.y = 0.0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !game_core.player.is_moving {
|
// Handle updating the stun timer
|
||||||
game_core.player.position -= player_real_movement;
|
if player_stunned {
|
||||||
}
|
game_core.player.stun_timer -= dt;
|
||||||
} else {
|
|
||||||
game_core.player.is_moving = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the camera to follow the player
|
// Move the camera to follow the player
|
||||||
@ -168,61 +203,3 @@ pub fn update_player_movement(
|
|||||||
// game_core.master_camera.target.y = -100.0;
|
// game_core.master_camera.target.y = -100.0;
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
use raylib::prelude::*;
|
use raylib::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{gamecore::{GameCore, GameState}, lib::{utils::calculate_linear_slide, wrappers::audio::player::AudioPlayer}};
|
||||||
gamecore::{GameCore, GameState},
|
|
||||||
lib::wrappers::audio::player::AudioPlayer,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::screen::Screen;
|
use super::screen::Screen;
|
||||||
|
|
||||||
@ -32,14 +29,7 @@ impl LoadingScreen {
|
|||||||
|
|
||||||
fn get_logo_mask(&self, playthrough_percent: f64) -> Color {
|
fn get_logo_mask(&self, playthrough_percent: f64) -> Color {
|
||||||
// Determine the alpha
|
// Determine the alpha
|
||||||
let alpha;
|
let alpha = calculate_linear_slide(playthrough_percent);
|
||||||
if playthrough_percent < 0.25 {
|
|
||||||
alpha = playthrough_percent / 0.25
|
|
||||||
} else if playthrough_percent > 0.75 {
|
|
||||||
alpha = 1.0 - ((playthrough_percent - 0.75) / 0.25);
|
|
||||||
} else {
|
|
||||||
alpha = 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build a color mask
|
// Build a color mask
|
||||||
Color {
|
Color {
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
use raylib::prelude::*;
|
use raylib::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{gamecore::{GameCore, GameState}, lib::wrappers::audio::player::AudioPlayer, pallette::WATER_DARK};
|
||||||
gamecore::{GameCore, GameState},
|
|
||||||
lib::wrappers::audio::player::AudioPlayer,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::screen::Screen;
|
use super::screen::Screen;
|
||||||
|
|
||||||
@ -34,40 +31,48 @@ impl Screen for MainMenuScreen {
|
|||||||
draw_handle.draw_text(
|
draw_handle.draw_text(
|
||||||
"ONE BREATH",
|
"ONE BREATH",
|
||||||
(win_height / 2) - 80,
|
(win_height / 2) - 80,
|
||||||
win_width / 4,
|
win_width / 8,
|
||||||
40,
|
80,
|
||||||
Color::BLACK,
|
Color::BLACK,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Get mouse position data
|
||||||
|
let mouse_position = draw_handle.get_mouse_position();
|
||||||
|
let hovering_play_button = mouse_position.y > (win_width as f32 / 4.0)
|
||||||
|
&& mouse_position.y < (win_width as f32 / 4.0) + 60.0;
|
||||||
|
let hovering_quit_button = mouse_position.y > (win_width as f32 / 4.0) + 100.0
|
||||||
|
&& mouse_position.y < (win_width as f32 / 4.0) + 160.0;
|
||||||
|
|
||||||
// Play and quit
|
// Play and quit
|
||||||
draw_handle.draw_text(
|
draw_handle.draw_text(
|
||||||
"Play",
|
"Play",
|
||||||
(win_height / 2) - 80,
|
(win_height / 2) + 120,
|
||||||
(win_width / 4) + 100,
|
(win_width / 4),
|
||||||
20,
|
60,
|
||||||
Color::BLACK,
|
match hovering_play_button {
|
||||||
|
true => Color::GREEN,
|
||||||
|
false => Color::BLACK,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
draw_handle.draw_text(
|
draw_handle.draw_text(
|
||||||
"Quit",
|
"Quit",
|
||||||
(win_height / 2) - 80,
|
(win_height / 2) + 130,
|
||||||
(win_width / 4) + 140,
|
(win_width / 4) + 100,
|
||||||
20,
|
60,
|
||||||
Color::BLACK,
|
match hovering_quit_button {
|
||||||
|
true => Color::GREEN,
|
||||||
|
false => Color::BLACK,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle button presses
|
// Handle button presses
|
||||||
let mouse_position = draw_handle.get_mouse_position();
|
|
||||||
let mouse_clicked = draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON);
|
let mouse_clicked = draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON);
|
||||||
|
|
||||||
// Check clicks
|
// Check clicks
|
||||||
if mouse_clicked {
|
if mouse_clicked {
|
||||||
if mouse_position.y > (win_width as f32 / 4.0) + 100.0
|
if hovering_play_button {
|
||||||
&& mouse_position.y < (win_width as f32 / 4.0) + 120.0
|
|
||||||
{
|
|
||||||
return Some(GameState::InGame);
|
return Some(GameState::InGame);
|
||||||
} else if mouse_position.y > (win_width as f32 / 4.0) + 140.0
|
} else if hovering_quit_button {
|
||||||
&& mouse_position.y < (win_width as f32 / 4.0) + 180.0
|
|
||||||
{
|
|
||||||
return Some(GameState::GameQuit);
|
return Some(GameState::GameQuit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,6 +134,7 @@ fn main() {
|
|||||||
profiler.data.player_coins = game_core.player.coins;
|
profiler.data.player_coins = game_core.player.coins;
|
||||||
profiler.data.player_boost_percent = game_core.player.boost_percent;
|
profiler.data.player_boost_percent = game_core.player.boost_percent;
|
||||||
profiler.data.player_breath_percent = game_core.player.breath_percent;
|
profiler.data.player_breath_percent = game_core.player.breath_percent;
|
||||||
|
profiler.data.player_pose = game_core.player.position;
|
||||||
|
|
||||||
// Send telemetry data
|
// Send telemetry data
|
||||||
profiler.update();
|
profiler.update();
|
||||||
|
@ -33,4 +33,18 @@ pub const WATER: Color = Color {
|
|||||||
g: 66,
|
g: 66,
|
||||||
b: 143,
|
b: 143,
|
||||||
a: 255
|
a: 255
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const WATER_DARK: Color = Color {
|
||||||
|
r: 8,
|
||||||
|
g: 24,
|
||||||
|
b: 54,
|
||||||
|
a: 255
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const TRANSLUCENT_RED_64: Color = Color {
|
||||||
|
r: 230,
|
||||||
|
g: 41,
|
||||||
|
b: 55,
|
||||||
|
a: 64,
|
||||||
};
|
};
|
186
src/player.rs
186
src/player.rs
@ -1,18 +1,51 @@
|
|||||||
use raylib::math::{Rectangle, Vector2};
|
use crate::{
|
||||||
|
entities::enemy::base::EnemyBase,
|
||||||
|
gamecore::{GameCore, GameProgress},
|
||||||
|
items::{AirBag, Flashlight, Flippers, StunGun},
|
||||||
|
lib::utils::calculate_linear_slide,
|
||||||
|
pallette::{TRANSLUCENT_WHITE_64, TRANSLUCENT_WHITE_96},
|
||||||
|
resources::GlobalResources,
|
||||||
|
world::World,
|
||||||
|
};
|
||||||
|
use raylib::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::lib::utils::triangles::rotate_vector;
|
const AOE_RING_MAX_RADIUS: f32 = 60.0;
|
||||||
|
const STUN_ATTACK_TIME: f64 = 0.75;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||||
|
pub struct PlayerInventory {
|
||||||
|
pub stun_gun: Option<StunGun>,
|
||||||
|
pub air_bag: Option<AirBag>,
|
||||||
|
pub flashlight: Option<Flashlight>,
|
||||||
|
pub flippers: Option<Flippers>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlayerInventory {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
stun_gun: Some(StunGun::lvl1()), //TMP
|
||||||
|
flashlight: Some(Flashlight::lvl1()), //TMP
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
pub position: Vector2,
|
pub position: Vector2,
|
||||||
pub direction: Vector2,
|
pub direction: Vector2,
|
||||||
pub size: Vector2,
|
pub size: Vector2,
|
||||||
|
pub radius: f32,
|
||||||
pub coins: u32,
|
pub coins: u32,
|
||||||
pub boost_percent: f32,
|
pub boost_percent: f32,
|
||||||
pub breath_percent: f32,
|
pub breath_percent: f32,
|
||||||
pub is_moving: bool,
|
pub is_moving: bool,
|
||||||
pub is_boosting: bool,
|
pub is_boosting: bool,
|
||||||
pub is_boost_charging: bool,
|
pub is_boost_charging: bool,
|
||||||
|
pub inventory: PlayerInventory,
|
||||||
|
pub stun_timer: f64,
|
||||||
|
pub attacking_timer: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
@ -21,7 +54,10 @@ impl Player {
|
|||||||
boost_percent: 1.0,
|
boost_percent: 1.0,
|
||||||
size: Vector2 { x: 11.0, y: 21.0 },
|
size: Vector2 { x: 11.0, y: 21.0 },
|
||||||
breath_percent: 1.0,
|
breath_percent: 1.0,
|
||||||
|
is_moving: true,
|
||||||
|
radius: 4.5,
|
||||||
position: spawn.clone(),
|
position: spawn.clone(),
|
||||||
|
inventory: PlayerInventory::new(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,6 +90,150 @@ impl Player {
|
|||||||
// || rectangle.check_collision_point_rec(top_right_corner)
|
// || rectangle.check_collision_point_rec(top_right_corner)
|
||||||
// || rectangle.check_collision_point_rec(bottom_left_corner);
|
// || rectangle.check_collision_point_rec(bottom_left_corner);
|
||||||
|
|
||||||
return rectangle.check_collision_circle_rec(self.position, (self.size.y * 0.5) / 2.0);
|
return rectangle.check_collision_circle_rec(self.position, self.radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stun the player
|
||||||
|
pub fn set_stun_seconds(&mut self, seconds: f64) {
|
||||||
|
self.stun_timer = seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to attack with the stun gun
|
||||||
|
pub fn begin_attack(&mut self, world: &mut World, current_time: f64) {
|
||||||
|
if self.inventory.stun_gun.is_some() && !self.is_stunned() {
|
||||||
|
self.attacking_timer = self.inventory.stun_gun.as_ref().unwrap().duration;
|
||||||
|
|
||||||
|
// Stun everything in reach
|
||||||
|
let stun_reach = self.inventory.stun_gun.as_ref().unwrap().range;
|
||||||
|
|
||||||
|
for jellyfish in world.jellyfish.iter_mut() {
|
||||||
|
if jellyfish.position.distance_to(self.position).abs() <= stun_reach {
|
||||||
|
jellyfish.handle_getting_attacked(self.attacking_timer, current_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for octopus in world.octopus.iter_mut() {
|
||||||
|
if octopus.current_position.distance_to(self.position).abs() <= stun_reach {
|
||||||
|
octopus.handle_getting_attacked(self.attacking_timer, current_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_stun_gun_active(&self) -> bool {
|
||||||
|
return self.attacking_timer != 0.0 && self.inventory.stun_gun.is_some();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_stunned(&self) -> bool {
|
||||||
|
return self.stun_timer > 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate how far the player is
|
||||||
|
pub fn calculate_depth_percent(&self, world: &World) -> f32 {
|
||||||
|
let dist_from_player_to_end = self.position.distance_to(world.end_position);
|
||||||
|
let dist_from_start_to_end = Vector2::zero().distance_to(world.end_position);
|
||||||
|
return ((dist_from_start_to_end - dist_from_player_to_end) / dist_from_start_to_end)
|
||||||
|
.clamp(0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create GameProgress from the current life
|
||||||
|
pub fn create_statistics(&self, game_core: &GameCore, current_time: f64) -> GameProgress {
|
||||||
|
GameProgress {
|
||||||
|
coins: self.coins,
|
||||||
|
inventory: self.inventory.clone(),
|
||||||
|
max_depth: self.calculate_depth_percent(&game_core.world),
|
||||||
|
fastest_time: Some(current_time - game_core.last_state_change_time),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render the player
|
||||||
|
pub fn render(
|
||||||
|
&mut self,
|
||||||
|
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
|
||||||
|
resources: &mut GlobalResources,
|
||||||
|
dt: f64,
|
||||||
|
) {
|
||||||
|
// Convert the player direction to a rotation
|
||||||
|
let player_rotation = Vector2::zero().angle_to(self.direction);
|
||||||
|
|
||||||
|
// Render the player's boost ring
|
||||||
|
// This functions both as a breath meter, and as a boost meter
|
||||||
|
let boost_ring_max_radius = self.size.x + 5.0;
|
||||||
|
context_2d.draw_circle(
|
||||||
|
self.position.x as i32,
|
||||||
|
self.position.y as i32,
|
||||||
|
boost_ring_max_radius * self.boost_percent,
|
||||||
|
TRANSLUCENT_WHITE_64,
|
||||||
|
);
|
||||||
|
context_2d.draw_ring(
|
||||||
|
Vector2 {
|
||||||
|
x: self.position.x as i32 as f32,
|
||||||
|
y: self.position.y as i32 as f32,
|
||||||
|
},
|
||||||
|
boost_ring_max_radius,
|
||||||
|
boost_ring_max_radius + 1.0,
|
||||||
|
0,
|
||||||
|
(360.0 * self.breath_percent) as i32,
|
||||||
|
0,
|
||||||
|
TRANSLUCENT_WHITE_96,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate AOE ring
|
||||||
|
if self.is_stun_gun_active() {
|
||||||
|
let animation_progression =
|
||||||
|
self.attacking_timer / self.inventory.stun_gun.as_ref().unwrap().duration;
|
||||||
|
let aoe_ring = calculate_linear_slide(animation_progression) as f32;
|
||||||
|
self.attacking_timer = (self.attacking_timer - dt).max(0.0);
|
||||||
|
|
||||||
|
// Render attack AOE
|
||||||
|
if animation_progression >= 0.5 {
|
||||||
|
context_2d.draw_circle_lines(
|
||||||
|
self.position.x as i32,
|
||||||
|
self.position.y as i32,
|
||||||
|
self.inventory.stun_gun.as_ref().unwrap().range * aoe_ring,
|
||||||
|
TRANSLUCENT_WHITE_64.fade(aoe_ring),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
context_2d.draw_circle_lines(
|
||||||
|
self.position.x as i32,
|
||||||
|
self.position.y as i32,
|
||||||
|
self.inventory.stun_gun.as_ref().unwrap().range,
|
||||||
|
TRANSLUCENT_WHITE_64.fade(aoe_ring),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the player based on what is happening
|
||||||
|
if self.is_stunned() {
|
||||||
|
resources.player_animation_stunned.draw(
|
||||||
|
context_2d,
|
||||||
|
self.position,
|
||||||
|
player_rotation.to_degrees() - 90.0,
|
||||||
|
);
|
||||||
|
} else if self.is_boost_charging {
|
||||||
|
resources.player_animation_boost_charge.draw(
|
||||||
|
context_2d,
|
||||||
|
self.position,
|
||||||
|
player_rotation.to_degrees() - 90.0,
|
||||||
|
);
|
||||||
|
} else if self.is_boosting {
|
||||||
|
resources.player_animation_boost.draw(
|
||||||
|
context_2d,
|
||||||
|
self.position,
|
||||||
|
player_rotation.to_degrees() - 90.0,
|
||||||
|
);
|
||||||
|
} else if self.is_moving {
|
||||||
|
resources.player_animation_regular.draw(
|
||||||
|
context_2d,
|
||||||
|
self.position,
|
||||||
|
player_rotation.to_degrees() - 90.0,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
resources.player_animation_regular.draw_frame(
|
||||||
|
context_2d,
|
||||||
|
self.position,
|
||||||
|
player_rotation.to_degrees() - 90.0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,17 @@ pub struct GlobalResources {
|
|||||||
pub player_animation_regular: FrameAnimationWrapper,
|
pub player_animation_regular: FrameAnimationWrapper,
|
||||||
pub player_animation_boost_charge: FrameAnimationWrapper,
|
pub player_animation_boost_charge: FrameAnimationWrapper,
|
||||||
pub player_animation_boost: FrameAnimationWrapper,
|
pub player_animation_boost: FrameAnimationWrapper,
|
||||||
|
pub player_animation_stunned: FrameAnimationWrapper,
|
||||||
|
|
||||||
// Cave
|
// Cave
|
||||||
pub cave_mid_layer: Texture2D
|
pub cave_mid_layer: Texture2D,
|
||||||
|
|
||||||
|
// Enemies
|
||||||
|
pub jellyfish_animation_regular: FrameAnimationWrapper,
|
||||||
|
pub jellyfish_animation_attack: FrameAnimationWrapper,
|
||||||
|
|
||||||
|
// Darkness layer
|
||||||
|
pub darkness_overlay: Texture2D
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalResources {
|
impl GlobalResources {
|
||||||
@ -59,10 +67,41 @@ impl GlobalResources {
|
|||||||
21,
|
21,
|
||||||
30,
|
30,
|
||||||
),
|
),
|
||||||
|
player_animation_stunned: FrameAnimationWrapper::new(
|
||||||
|
raylib.load_texture_from_image(
|
||||||
|
&thread,
|
||||||
|
&Image::load_image("./assets/img/character/stunned.png")?,
|
||||||
|
)?,
|
||||||
|
Vector2 { x: 12.0, y: 22.0 },
|
||||||
|
4,
|
||||||
|
100 / 8,
|
||||||
|
),
|
||||||
cave_mid_layer: raylib.load_texture_from_image(
|
cave_mid_layer: raylib.load_texture_from_image(
|
||||||
&thread,
|
&thread,
|
||||||
&Image::load_image("./assets/img/map/cave.png")?,
|
&Image::load_image("./assets/img/map/cave.png")?,
|
||||||
)?,
|
)?,
|
||||||
|
jellyfish_animation_regular: FrameAnimationWrapper::new(
|
||||||
|
raylib.load_texture_from_image(
|
||||||
|
&thread,
|
||||||
|
&Image::load_image("./assets/img/enemies/jelly.png")?,
|
||||||
|
)?,
|
||||||
|
Vector2 { x: 10.0, y: 10.0 },
|
||||||
|
6,
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
jellyfish_animation_attack: FrameAnimationWrapper::new(
|
||||||
|
raylib.load_texture_from_image(
|
||||||
|
&thread,
|
||||||
|
&Image::load_image("./assets/img/enemies/jellyAttack.png")?,
|
||||||
|
)?,
|
||||||
|
Vector2 { x: 20.0, y: 20.0 },
|
||||||
|
15,
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
darkness_overlay: raylib.load_texture_from_image(
|
||||||
|
&thread,
|
||||||
|
&Image::load_image("./assets/img/map/darkness.png")?,
|
||||||
|
)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
|
|
||||||
use crate::entities::fish::FishEntity;
|
use crate::entities::{enemy::{jellyfish::JellyFish, octopus::Octopus}, fish::FishEntity};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct World {
|
pub struct World {
|
||||||
@ -19,7 +19,11 @@ pub struct World {
|
|||||||
pub fish: Vec<FishEntity>,
|
pub fish: Vec<FishEntity>,
|
||||||
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub colliders: Vec<Rectangle>
|
pub colliders: Vec<Rectangle>,
|
||||||
|
|
||||||
|
pub jellyfish: Vec<JellyFish>,
|
||||||
|
pub octopus: Vec<Octopus>,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl World {
|
impl World {
|
||||||
|
Reference in New Issue
Block a user