diff --git a/.gitignore b/.gitignore index 4e1d2b0..2fb3a89 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ Cargo.lock /target -.project \ No newline at end of file +.project +savestate.json \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index e7bbe5a..06322a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "one-breath" +name = "pink-man-swim" version = "0.1.0" authors = ["Evan Pratten "] edition = "2018" diff --git a/assets/shaders/pixel.fs b/assets/shaders/pixel.fs new file mode 100644 index 0000000..2148371 --- /dev/null +++ b/assets/shaders/pixel.fs @@ -0,0 +1,32 @@ +#version 330 + +// Input vertex attributes (from vertex shader) +in vec2 fragTexCoord; +in vec4 fragColor; + +// Input uniform values +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +// Output fragment color +out vec4 finalColor; + +// Viewport dimensions +const float renderWidth = 1080; +const float renderHeight = 720; + +// Pixel scaling +uniform float pixelWidth = 2.0; +uniform float pixelHeight = 2.0; + +void main() +{ + float dx = pixelWidth * (1.0 / renderWidth); + float dy = pixelHeight * (1.0 / renderHeight); + + vec2 coord = vec2(dx * floor(fragTexCoord.x / dx), dy * floor(fragTexCoord.y / dy)); + + vec3 tc = texture(texture0, coord).rgb; + + finalColor = vec4(tc, 1.0); +} diff --git a/bundle/create-releases.sh b/bundle/create-releases.sh index 1005aa0..a098a41 100755 --- a/bundle/create-releases.sh +++ b/bundle/create-releases.sh @@ -9,10 +9,10 @@ set -e # Make a uni-bundle echo "Creating a fat bundle for all platforms" rm -rf ./bundle/release -rm -rf ./bundle/one-breath.zip +rm -rf ./bundle/pink-man-swim.zip mkdir -p ./bundle/release cp -r ./assets ./bundle/release -cp ./bundle/linux/release/one-breath ./bundle/release/one-breath -cp ./bundle/windows/release/one-breath.exe ./bundle/release/one-breath.exe +cp ./bundle/linux/release/pink-man-swim ./bundle/release/pink-man-swim +cp ./bundle/windows/release/pink-man-swim.exe ./bundle/release/pink-man-swim.exe cd ./bundle/release -zip -r ../one-breath.zip ./ \ No newline at end of file +zip -r ../pink-man-swim.zip ./ \ No newline at end of file diff --git a/bundle/linux/create-release.sh b/bundle/linux/create-release.sh index 6e4b674..6b69fe3 100755 --- a/bundle/linux/create-release.sh +++ b/bundle/linux/create-release.sh @@ -12,7 +12,7 @@ rm -rf ./bundle/linux/release-x86_64-unknown-linux-gnu.zip mkdir -p ./bundle/linux/release echo "Copying binary" -cp ./target/x86_64-unknown-linux-gnu/release/one-breath ./bundle/linux/release +cp ./target/x86_64-unknown-linux-gnu/release/pink-man-swim ./bundle/linux/release echo "Copying assets" cp -r ./assets ./bundle/linux/release diff --git a/bundle/windows/create-release.sh b/bundle/windows/create-release.sh index bf5c147..2bc5c5d 100755 --- a/bundle/windows/create-release.sh +++ b/bundle/windows/create-release.sh @@ -11,7 +11,7 @@ rm -rf ./bundle/windows/release-x86_64-pc-windows-gnu.zip mkdir -p ./bundle/windows/release echo "Copying binary" -cp ./target/x86_64-pc-windows-gnu/release/one-breath.exe ./bundle/windows/release +cp ./target/x86_64-pc-windows-gnu/release/pink-man-swim.exe ./bundle/windows/release echo "Copying assets" cp -r ./assets ./bundle/windows/release diff --git a/src/entities/enemy/octopus.rs b/src/entities/enemy/octopus.rs index 895d8c8..e3d4869 100644 --- a/src/entities/enemy/octopus.rs +++ b/src/entities/enemy/octopus.rs @@ -1,4 +1,8 @@ -use crate::{lib::utils::calculate_linear_slide, pallette::{TRANSLUCENT_RED_64, TRANSLUCENT_WHITE_128, TRANSLUCENT_WHITE_64}, player::Player}; +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}; @@ -71,7 +75,8 @@ impl EnemyBase for Octopus { // 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 == 0.0 + && !is_octopus_stunned { self.suck_air_time_remaining = OCTOPUS_SUCK_AIR_DURATION; self.has_taken_air_from_player = false; @@ -107,15 +112,22 @@ impl EnemyBase for Octopus { self.suck_air_bubbles.clear(); } - // TODO: TMP - context_2d.draw_circle_v(self.current_position, 10.0, Color::RED); + // Render animation + if self.suck_air_time_remaining > 0.0 { + resources + .octopus_animation_attack + .draw(context_2d, self.current_position, 0.0); + } else { + resources + .octopus_animation_regular + .draw(context_2d, self.current_position, 0.0); + } } 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 diff --git a/src/gamecore.rs b/src/gamecore.rs index 0954c0f..3013213 100644 --- a/src/gamecore.rs +++ b/src/gamecore.rs @@ -25,7 +25,8 @@ pub enum GameState { GameQuit, InGame, GameEnd, - InShop + InShop, + WinGame } impl fmt::Display for GameState { @@ -87,7 +88,7 @@ impl GameProgress { // self.fastest_time = self.fastest_time.min(new_progress.fastest_time); // Write to file - let result = self.to_file("./assets/savestate.json".to_string()); + let result = self.to_file("./savestate.json".to_string()); if result.is_err() { println!("Could not save game state. Holding in RAM"); } diff --git a/src/logic/gameend.rs b/src/logic/gameend.rs index eeb7136..c20064e 100644 --- a/src/logic/gameend.rs +++ b/src/logic/gameend.rs @@ -25,9 +25,8 @@ impl Screen for GameEndScreen { audio_system: &mut AudioPlayer, game_core: &mut GameCore, ) -> Option { - let mouse_position = draw_handle.get_mouse_position(); - // draw_handle.clear_background(Color::GRAY); - // // TODO: Maybe we can stick some art here? + draw_handle.clear_background(Color::GRAY); + // TODO: Maybe we can stick some art here? // Render the background draw_handle.draw_texture(&game_core.resources.shop_background, 0, 0, Color::WHITE); @@ -71,7 +70,7 @@ impl Screen for GameEndScreen { Color::BLACK, ); - // Render button + // Creates let go_to_menu_button = OnScreenButton::new( String::from("Return to shop"), Rectangle { @@ -88,8 +87,10 @@ impl Screen for GameEndScreen { true, ); + // render button go_to_menu_button.render(draw_handle); + // If the player clicks on the button send them to shop if go_to_menu_button.is_hovered(draw_handle) && draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON){ game_core.switch_state(GameState::InShop, Some(draw_handle)); diff --git a/src/logic/ingame/mod.rs b/src/logic/ingame/mod.rs index cb64580..0f1fac4 100644 --- a/src/logic/ingame/mod.rs +++ b/src/logic/ingame/mod.rs @@ -154,9 +154,6 @@ impl Screen for InGameScreen { // Calculate DT let dt = draw_handle.get_time() - game_core.last_frame_time; - // Clear frame - draw_handle.clear_background(Color::BLACK); - // Handle the pause menu being opened if draw_handle.is_key_pressed(KeyboardKey::KEY_ESCAPE) { return Some(GameState::PauseMenu); @@ -176,38 +173,68 @@ impl Screen for InGameScreen { // 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, dt); - if game_core.show_simple_debug_info { - self.render_colliders(&mut context_2d, game_core); + unsafe { + raylib::ffi::BeginTextureMode(*game_core.resources.shader_texture); } + { + let mut context_2d = draw_handle.begin_mode2D(game_core.master_camera); - // Render entities - 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.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, - ); - } + // Clear frame + context_2d.clear_background(Color::BLACK); - // Render Player - game_core - .player - .render(&mut context_2d, &mut game_core.resources, dt); + // Render the world + 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 + 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.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 + game_core + .player + .render(&mut context_2d, &mut game_core.resources, dt); + } + unsafe { + raylib::ffi::EndTextureMode(); + } + } + + // Render the 2D context via the ripple shader + { + let mut shader_context = + draw_handle.begin_shader_mode(&game_core.resources.pixel_shader); + + // Blit the texture + shader_context.draw_texture_rec( + &game_core.resources.shader_texture, + Rectangle { + x: 0.0, + y: 0.0, + width: game_core.resources.shader_texture.width() as f32, + height: (game_core.resources.shader_texture.height() as f32) * -1.0, + }, + Vector2::zero(), + Color::WHITE, + ); } // Render the darkness layer @@ -216,12 +243,15 @@ impl Screen for InGameScreen { // 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); } + if game_core.world.end_position.distance_to(game_core.player.position) <= 70.0{ + return Some(GameState::WinGame); + } + return None; } } diff --git a/src/logic/mainmenu.rs b/src/logic/mainmenu.rs index 6ea0d94..89f7d87 100644 --- a/src/logic/mainmenu.rs +++ b/src/logic/mainmenu.rs @@ -35,8 +35,8 @@ impl Screen for MainMenuScreen { // Render title draw_handle.draw_text( - "ONE BREATH", - (win_height / 2) - 80, + "PINK MAN SWIM", + (win_height / 2) - 120, win_width / 8, 80, Color::BLACK, diff --git a/src/logic/mod.rs b/src/logic/mod.rs index 7b1e5eb..9942ba6 100644 --- a/src/logic/mod.rs +++ b/src/logic/mod.rs @@ -4,4 +4,5 @@ pub mod mainmenu; pub mod pausemenu; pub mod ingame; pub mod gameend; -pub mod shop; \ No newline at end of file +pub mod shop; +pub mod winscreen; \ No newline at end of file diff --git a/src/logic/winscreen.rs b/src/logic/winscreen.rs new file mode 100644 index 0000000..76628bb --- /dev/null +++ b/src/logic/winscreen.rs @@ -0,0 +1,93 @@ +use raylib::prelude::*; + +use crate::lib::utils::button::OnScreenButton; +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 WinScreen {} + +impl WinScreen { + pub fn new() -> Self { + return Self {}; + } +} + +impl Screen for WinScreen { + fn render( + &mut self, + draw_handle: &mut RaylibDrawHandle, + thread: &RaylibThread, + audio_system: &mut AudioPlayer, + game_core: &mut GameCore, + ) -> Option { + 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( + "You've Won!!", + (win_width / 2) - ((SCREEN_PANEL_SIZE.x as i32 + 6) / 2) + 60, + (win_height / 2) - (SCREEN_PANEL_SIZE.y as i32 / 2) + 10, + 30, + Color::BLACK, + ); + + // Render message + draw_handle.draw_text( + "You can use the transponder to \ncontact help!", + ((win_width / 2) - ((SCREEN_PANEL_SIZE.x as i32 + 6) / 2)) + (0.15 * SCREEN_PANEL_SIZE.x)as i32, + (win_height / 2) - (SCREEN_PANEL_SIZE.y as i32 / 2) + 80, + 15, + Color::BLACK, + ); + + // Render button + let go_to_menu_button = OnScreenButton::new( + String::from("Return to menu"), + Rectangle { + x: (((win_width / 2) - ((SCREEN_PANEL_SIZE.x as i32 + 6) / 2) + 5) + + (0.15 * SCREEN_PANEL_SIZE.x) as i32) as f32, + y: (((win_height / 2) - (SCREEN_PANEL_SIZE.y as i32 / 2) + 90) as f32) + 100.0, + width: 210.0, + height: 50.0, + }, + Color::WHITE, + Color::BLACK, + Color::GRAY, + 25, + true, + ); + + go_to_menu_button.render(draw_handle); + + if go_to_menu_button.is_hovered(draw_handle) + && draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON) + { + game_core.switch_state(GameState::MainMenu, Some(draw_handle)); + } + + return None; + } +} diff --git a/src/main.rs b/src/main.rs index 64c8e2a..c4822d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,10 +11,7 @@ mod world; use gamecore::{GameCore, GameProgress, GameState}; use lib::{utils::profiler::GameProfiler, wrappers::audio::player::AudioPlayer}; use log::info; -use logic::{ - gameend::GameEndScreen, ingame::InGameScreen, loadingscreen::LoadingScreen, - mainmenu::MainMenuScreen, pausemenu::PauseMenuScreen, screen::Screen, shop::ShopScreen, -}; +use logic::{gameend::GameEndScreen, ingame::InGameScreen, loadingscreen::LoadingScreen, mainmenu::MainMenuScreen, pausemenu::PauseMenuScreen, screen::Screen, shop::ShopScreen, winscreen::{self, WinScreen}}; use raylib::prelude::*; use world::{load_world_colliders, World}; @@ -54,7 +51,7 @@ fn main() { .expect("Failed to load main world JSON"); // Load the game progress - let game_progress = GameProgress::try_from_file("./assets/savestate.json".to_string()); + let game_progress = GameProgress::try_from_file("./savestate.json".to_string()); // Set up the game's core state let mut game_core = GameCore::new(&mut raylib, &raylib_thread, world, game_progress); @@ -75,6 +72,7 @@ fn main() { let mut ingame_screen = InGameScreen::new(); let mut game_end_screen = GameEndScreen::new(); let mut shop_screen = ShopScreen::new(); + let mut win_screen = WinScreen::new(); // Main rendering loop while !raylib.window_should_close() { @@ -119,6 +117,12 @@ fn main() { &mut audio_system, &mut game_core, ), + GameState::WinGame => win_screen.render( + &mut draw_handle, + &raylib_thread, + &mut audio_system, + &mut game_core, + ) }; // If needed, update the global state diff --git a/src/resources.rs b/src/resources.rs index 5fb73dc..83c7b29 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -1,9 +1,5 @@ use failure::Error; -use raylib::{ - math::Vector2, - texture::{Image, Texture2D}, - RaylibHandle, RaylibThread, -}; +use raylib::{RaylibHandle, RaylibThread, math::Vector2, shaders::Shader, texture::{Image, RenderTexture2D, Texture2D}}; use crate::lib::wrappers::animation::FrameAnimationWrapper; @@ -24,10 +20,14 @@ pub struct GlobalResources { // Cave pub cave_mid_layer: Texture2D, + pub pixel_shader: Shader, + pub shader_texture: RenderTexture2D, // Enemies pub jellyfish_animation_regular: FrameAnimationWrapper, pub jellyfish_animation_attack: FrameAnimationWrapper, + pub octopus_animation_regular: FrameAnimationWrapper, + pub octopus_animation_attack: FrameAnimationWrapper, // Darkness layer pub darkness_overlay: Texture2D, @@ -125,6 +125,8 @@ impl GlobalResources { &thread, &Image::load_image("./assets/img/map/cave.png")?, )?, + pixel_shader: raylib.load_shader(&thread, None, Some("./assets/shaders/pixel.fs"))?, + shader_texture: raylib.load_render_texture(&thread, raylib.get_screen_width() as u32, raylib.get_screen_height() as u32)?, jellyfish_animation_regular: FrameAnimationWrapper::new( raylib.load_texture_from_image( &thread, @@ -143,6 +145,24 @@ impl GlobalResources { 15, 4, ), + octopus_animation_regular: FrameAnimationWrapper::new( + raylib.load_texture_from_image( + &thread, + &Image::load_image("./assets/img/enemies/octopus.png")?, + )?, + Vector2 { x: 20.0, y: 20.0 }, + 6, + 4, + ), + octopus_animation_attack: FrameAnimationWrapper::new( + raylib.load_texture_from_image( + &thread, + &Image::load_image("./assets/img/enemies/octopusSuck.png")?, + )?, + Vector2 { x: 30.0, y: 20.0 }, + 4, + 4, + ), darkness_overlay: raylib.load_texture_from_image( &thread, &Image::load_image("./assets/img/map/darkness.png")?,