Merge pull request #20 from Ewpratten/ouchies

Add jellyfish as an enemy
This commit is contained in:
Evan Pratten 2021-04-24 19:03:58 -04:00 committed by GitHub
commit 561bb5abf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 751 additions and 131 deletions

View 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": [
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

Binary file not shown.

View File

@ -0,0 +1,66 @@
{ "frames": {
"Sprite-0001 0.": {
"frame": { "x": 0, "y": 0, "w": 10, "h": 10 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 10, "h": 10 },
"sourceSize": { "w": 10, "h": 10 },
"duration": 300
},
"Sprite-0001 1.": {
"frame": { "x": 10, "y": 0, "w": 10, "h": 10 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 10, "h": 10 },
"sourceSize": { "w": 10, "h": 10 },
"duration": 300
},
"Sprite-0001 2.": {
"frame": { "x": 20, "y": 0, "w": 10, "h": 10 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 10, "h": 10 },
"sourceSize": { "w": 10, "h": 10 },
"duration": 300
},
"Sprite-0001 3.": {
"frame": { "x": 30, "y": 0, "w": 10, "h": 10 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 10, "h": 10 },
"sourceSize": { "w": 10, "h": 10 },
"duration": 300
},
"Sprite-0001 4.": {
"frame": { "x": 40, "y": 0, "w": 10, "h": 10 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 10, "h": 10 },
"sourceSize": { "w": 10, "h": 10 },
"duration": 300
},
"Sprite-0001 5.": {
"frame": { "x": 50, "y": 0, "w": 10, "h": 10 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 10, "h": 10 },
"sourceSize": { "w": 10, "h": 10 },
"duration": 300
}
},
"meta": {
"app": "http://www.aseprite.org/",
"version": "1.2.27-x64",
"image": "jelly.png",
"format": "RGBA8888",
"size": { "w": 60, "h": 10 },
"scale": "1",
"frameTags": [
],
"layers": [
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" }
],
"slices": [
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

View File

@ -0,0 +1,138 @@
{ "frames": {
"jelly 0.aseprite": {
"frame": { "x": 0, "y": 0, "w": 20, "h": 20 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
"sourceSize": { "w": 20, "h": 20 },
"duration": 200
},
"jelly 1.aseprite": {
"frame": { "x": 20, "y": 0, "w": 20, "h": 20 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
"sourceSize": { "w": 20, "h": 20 },
"duration": 200
},
"jelly 2.aseprite": {
"frame": { "x": 40, "y": 0, "w": 20, "h": 20 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
"sourceSize": { "w": 20, "h": 20 },
"duration": 200
},
"jelly 3.aseprite": {
"frame": { "x": 60, "y": 0, "w": 20, "h": 20 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
"sourceSize": { "w": 20, "h": 20 },
"duration": 200
},
"jelly 4.aseprite": {
"frame": { "x": 80, "y": 0, "w": 20, "h": 20 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
"sourceSize": { "w": 20, "h": 20 },
"duration": 200
},
"jelly 5.aseprite": {
"frame": { "x": 100, "y": 0, "w": 20, "h": 20 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
"sourceSize": { "w": 20, "h": 20 },
"duration": 200
},
"jelly 6.aseprite": {
"frame": { "x": 120, "y": 0, "w": 20, "h": 20 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
"sourceSize": { "w": 20, "h": 20 },
"duration": 200
},
"jelly 7.aseprite": {
"frame": { "x": 140, "y": 0, "w": 20, "h": 20 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
"sourceSize": { "w": 20, "h": 20 },
"duration": 200
},
"jelly 8.aseprite": {
"frame": { "x": 160, "y": 0, "w": 20, "h": 20 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
"sourceSize": { "w": 20, "h": 20 },
"duration": 200
},
"jelly 9.aseprite": {
"frame": { "x": 180, "y": 0, "w": 20, "h": 20 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
"sourceSize": { "w": 20, "h": 20 },
"duration": 200
},
"jelly 10.aseprite": {
"frame": { "x": 200, "y": 0, "w": 20, "h": 20 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
"sourceSize": { "w": 20, "h": 20 },
"duration": 200
},
"jelly 11.aseprite": {
"frame": { "x": 220, "y": 0, "w": 20, "h": 20 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
"sourceSize": { "w": 20, "h": 20 },
"duration": 200
},
"jelly 12.aseprite": {
"frame": { "x": 240, "y": 0, "w": 20, "h": 20 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
"sourceSize": { "w": 20, "h": 20 },
"duration": 200
},
"jelly 13.aseprite": {
"frame": { "x": 260, "y": 0, "w": 20, "h": 20 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
"sourceSize": { "w": 20, "h": 20 },
"duration": 200
},
"jelly 14.aseprite": {
"frame": { "x": 280, "y": 0, "w": 20, "h": 20 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
"sourceSize": { "w": 20, "h": 20 },
"duration": 200
}
},
"meta": {
"app": "http://www.aseprite.org/",
"version": "1.2.27-x64",
"image": "jellyAttack.png",
"format": "RGBA8888",
"size": { "w": 300, "h": 20 },
"scale": "1",
"frameTags": [
],
"layers": [
{ "name": "Layer 3", "opacity": 255, "blendMode": "normal" }
],
"slices": [
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,14 @@
use raylib::prelude::*;
use crate::{player::Player, resources::GlobalResources};
pub trait EnemyBase {
fn render(
&mut self,
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
resources: &mut GlobalResources,
dt: f64,
);
fn handle_logic(&mut self, player: &mut Player, dt: f64);
fn handle_getting_attacked(&mut self, stun_duration: f64);
}

View File

@ -0,0 +1,92 @@
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>,
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) {
self.stunned_timer = stun_duration;
self.max_stunned_time = stun_duration;
}
}

View File

@ -0,0 +1,2 @@
pub mod base;
pub mod jellyfish;

View File

@ -1 +1,2 @@
pub mod fish;
pub mod enemy;

View File

@ -6,7 +6,11 @@ use raylib::{
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 log::debug;
@ -31,10 +35,10 @@ 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>,
pub coins: u32,
pub max_depth: f32,
pub fastest_time: Option<f64>,
pub inventory: PlayerInventory,
}
impl GameProgress {

View File

@ -1,10 +1,46 @@
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(tag = "t", content = "c")]
pub enum ShopItems {
StunGun(u8),
AirBag,
Flashlight(u8),
Flippers(u8)
pub struct StunGun {
pub range: f32,
pub duration: f64,
}
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;
#[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
}
}
}

View File

@ -1,2 +1,12 @@
pub mod profiler;
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;
}
}

View File

@ -1,3 +1,4 @@
use raylib::math::Vector2;
use serde_json::json;
use serialstudio::{
data::{DataGroup, DataSet, TelemetryFrame},
@ -21,7 +22,8 @@ pub struct ProfilerData {
// Player
pub player_coins: u32,
pub player_boost_percent: f32,
pub player_breath_percent: f32
pub player_breath_percent: f32,
pub player_pose: Vector2
}
/// The development profiler
@ -147,6 +149,20 @@ impl GameProfiler {
unit: Some("%".to_string()),
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,
},
],
},
],

View File

@ -2,7 +2,7 @@ use raylib::{core::color::Color, math::{Rectangle, Vector2}, prelude::{RaylibDra
/// A wrapper around an animation spritesheet
pub struct FrameAnimationWrapper {
sprite_sheet: Texture2D,
pub sprite_sheet: Texture2D,
size: Vector2,
frame_count: u32,
frames_per_second: u8,

View File

@ -8,13 +8,7 @@ pub fn render_hud(
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);
let progress = game_core.player.calculate_depth_percent(&game_core.world);
// Determine the progress slider position
let slider_bound_height = 20.0;

View File

@ -4,6 +4,7 @@ mod playerlogic;
use raylib::prelude::*;
use crate::{
entities::enemy::base::EnemyBase,
gamecore::{GameCore, GameState},
lib::wrappers::audio::player::AudioPlayer,
pallette::{SKY, WATER},
@ -31,6 +32,7 @@ impl InGameScreen {
&mut self,
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
game_core: &mut GameCore,
dt: f64
) {
// Build source bounds
let source_bounds = Rectangle {
@ -49,6 +51,13 @@ impl InGameScreen {
// Clear the background
context_2d.draw_rectangle_rec(world_bounds, WATER);
// 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
context_2d.draw_texture_rec(
&game_core.resources.cave_mid_layer,
@ -68,11 +77,7 @@ impl InGameScreen {
) {
// Render every collider
for collider in game_core.world.colliders.iter() {
context_2d.draw_rectangle_lines_ex(
collider,
1,
Color::RED,
);
context_2d.draw_rectangle_lines_ex(collider, 1, Color::RED);
}
}
}
@ -113,20 +118,21 @@ impl Screen for InGameScreen {
let mut context_2d = draw_handle.begin_mode2D(game_core.master_camera);
// 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 {
self.render_colliders(&mut context_2d, game_core);
}
// Render entities
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(&mut context_2d);
for jellyfish in game_core.world.jellyfish.iter_mut() {
jellyfish.handle_logic(&mut game_core.player, dt);
jellyfish.render(&mut context_2d, &mut game_core.resources, dt);
}
// Render Player
playerlogic::render_player(&mut context_2d, game_core);
game_core
.player
.render(&mut context_2d, &mut game_core.resources, dt);
}
// Render the hud

View File

@ -5,13 +5,13 @@ use crate::{
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 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;
const BREATH_DECREASE_PER_SECOND: f32 = 0.02;
pub fn update_player_movement(
draw_handle: &mut RaylibDrawHandle,
@ -65,7 +65,9 @@ pub fn update_player_movement(
}
// set 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
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_action = draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_RIGHT_BUTTON);
if user_request_action {
game_core.player.begin_attack(&mut game_core.world);
}
// Move the player in their direction
let speed_multiplier;
let mut speed_multiplier;
if user_request_boost && game_core.player.boost_percent >= 0.0 {
// Set the speed multiplier
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
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.is_moving = true;
game_core.player.position += player_real_movement;
let player_stunned = game_core.player.stun_timer > 0.0;
let mut 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.is_stunned()
{
if game_core.player.is_moving {
// move in x
game_core.player.position.x += player_real_movement.x;
// Check for any collisions
for collider in game_core.world.colliders.iter() {
if game_core.player.collides_with_rec(collider) {
game_core.player.is_moving = false;
game_core.player.position.x -= player_real_movement.x;
player_real_movement.x = 0.0;
break;
}
}
if !game_core.player.is_moving {
game_core.player.position -= player_real_movement;
// 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;
}
} else {
game_core.player.is_moving = false;
}
}
}
// Handle updating the stun timer
if player_stunned {
game_core.player.stun_timer -= dt;
}
// Move the camera to follow the player
@ -168,61 +203,3 @@ pub fn update_player_movement(
// 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,
);
}
}

View File

@ -1,9 +1,6 @@
use raylib::prelude::*;
use crate::{
gamecore::{GameCore, GameState},
lib::wrappers::audio::player::AudioPlayer,
};
use crate::{gamecore::{GameCore, GameState}, lib::{utils::calculate_linear_slide, wrappers::audio::player::AudioPlayer}};
use super::screen::Screen;
@ -32,14 +29,7 @@ impl LoadingScreen {
fn get_logo_mask(&self, playthrough_percent: f64) -> Color {
// Determine the alpha
let alpha;
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;
}
let alpha = calculate_linear_slide(playthrough_percent);
// Build a color mask
Color {

View File

@ -134,6 +134,7 @@ fn main() {
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;
profiler.data.player_pose = game_core.player.position;
// Send telemetry data
profiler.update();

View File

@ -34,3 +34,10 @@ pub const WATER: Color = Color {
b: 143,
a: 255
};
pub const TRANSLUCENT_RED_64: Color = Color {
r: 230,
g: 41,
b: 55,
a: 64,
};

View File

@ -1,18 +1,50 @@
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
..Default::default()
}
}
}
#[derive(Debug, Default)]
pub struct Player {
pub position: Vector2,
pub direction: Vector2,
pub size: Vector2,
pub radius: f32,
pub coins: u32,
pub boost_percent: f32,
pub breath_percent: f32,
pub is_moving: bool,
pub is_boosting: bool,
pub is_boost_charging: bool,
pub inventory: PlayerInventory,
pub stun_timer: f64,
pub attacking_timer: f64,
}
impl Player {
@ -21,7 +53,10 @@ impl Player {
boost_percent: 1.0,
size: Vector2 { x: 11.0, y: 21.0 },
breath_percent: 1.0,
is_moving: true,
radius: 4.5,
position: spawn.clone(),
inventory: PlayerInventory::new(),
..Default::default()
}
}
@ -54,6 +89,145 @@ impl Player {
// || rectangle.check_collision_point_rec(top_right_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) {
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);
}
}
}
}
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,
);
}
}
}

View File

@ -16,9 +16,14 @@ pub struct GlobalResources {
pub player_animation_regular: FrameAnimationWrapper,
pub player_animation_boost_charge: FrameAnimationWrapper,
pub player_animation_boost: FrameAnimationWrapper,
pub player_animation_stunned: FrameAnimationWrapper,
// Cave
pub cave_mid_layer: Texture2D
pub cave_mid_layer: Texture2D,
// Enemies
pub jellyfish_animation_regular: FrameAnimationWrapper,
pub jellyfish_animation_attack: FrameAnimationWrapper,
}
impl GlobalResources {
@ -59,10 +64,37 @@ impl GlobalResources {
21,
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(
&thread,
&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,
),
})
}
}

View File

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use std::io::Read;
use failure::Error;
use crate::entities::fish::FishEntity;
use crate::entities::{enemy::jellyfish::JellyFish, fish::FishEntity};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct World {
@ -19,7 +19,9 @@ pub struct World {
pub fish: Vec<FishEntity>,
#[serde(skip)]
pub colliders: Vec<Rectangle>
pub colliders: Vec<Rectangle>,
pub jellyfish: Vec<JellyFish>
}
impl World {