Initial commit

This commit is contained in:
Natty 2023-05-01 03:42:30 +02:00
commit 2e44b95059
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
27 changed files with 2332 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
/.idea

401
Cargo.lock generated Normal file
View File

@ -0,0 +1,401 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
]
[[package]]
name = "audir-sles"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea47348666a8edb7ad80cbee3940eb2bccf70df0e6ce09009abe1a836cb779f5"
[[package]]
name = "audrey"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58b92a84e89497e3cd25d3672cd5d1c288abaac02c18ff21283f17d118b889b8"
dependencies = [
"dasp_frame",
"dasp_sample",
"hound",
"lewton",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "blobcat-delivery"
version = "0.1.0"
dependencies = [
"macroquad",
]
[[package]]
name = "bumpalo"
version = "3.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8"
[[package]]
name = "bytemuck"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "dasp_frame"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2a3937f5fe2135702897535c8d4a5553f8b116f76c1529088797f2eee7c5cd6"
dependencies = [
"dasp_sample",
]
[[package]]
name = "dasp_sample"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
[[package]]
name = "fdeflate"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10"
dependencies = [
"simd-adler32",
]
[[package]]
name = "flate2"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "fontdue"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc"
dependencies = [
"hashbrown",
"ttf-parser",
]
[[package]]
name = "glam"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815"
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
[[package]]
name = "hound"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1"
[[package]]
name = "image"
version = "0.24.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"num-rational",
"num-traits",
"png",
]
[[package]]
name = "lewton"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d542c1a317036c45c2aa1cf10cc9d403ca91eb2d333ef1a4917e5cb10628bd0"
dependencies = [
"byteorder",
"ogg",
"smallvec",
]
[[package]]
name = "libc"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "macroquad"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3790f7fd2e4c480108cbfc86488f023b72e1e0bb6ffd5c6cba38049c7e2fbfc"
dependencies = [
"bumpalo",
"fontdue",
"glam",
"image",
"macroquad_macro",
"miniquad",
"quad-rand",
"quad-snd",
]
[[package]]
name = "macroquad_macro"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5cecfede1e530599c8686f7f2d609489101d3d63741a6dc423afc997ce3fcc8"
[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
dependencies = [
"libc",
]
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "miniquad"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46381fe09fbf91bfa402a3e4fc26a104c9130562d51f89964c46adbc00591496"
dependencies = [
"libc",
"ndk-sys",
"objc",
"winapi",
]
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
"simd-adler32",
]
[[package]]
name = "ndk-sys"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121"
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "objc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
"malloc_buf",
]
[[package]]
name = "ogg"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e571c3517af9e1729d4c63571a27edd660ade0667973bfc74a67c660c2b651"
dependencies = [
"byteorder",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "png"
version = "0.17.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa"
dependencies = [
"bitflags",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
name = "quad-alsa-sys"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66c2f04a6946293477973d85adc251d502da51c57b08cd9c997f0cfd8dcd4b5"
dependencies = [
"libc",
]
[[package]]
name = "quad-rand"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658fa1faf7a4cc5f057c9ee5ef560f717ad9d8dc66d975267f709624d6e1ab88"
[[package]]
name = "quad-snd"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53c954bb70493a2872775b74b663a767686e6d96d242e789d6a92cc4ebd2a64e"
dependencies = [
"audir-sles",
"audrey",
"libc",
"quad-alsa-sys",
"winapi",
]
[[package]]
name = "simd-adler32"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f"
[[package]]
name = "smallvec"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0"
dependencies = [
"maybe-uninit",
]
[[package]]
name = "ttf-parser"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

7
Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "blobcat-delivery"
version = "0.1.0"
edition = "2021"
[dependencies]
macroquad = "0.3"

BIN
assets/bike.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
assets/blobcat_basic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

BIN
assets/cloud1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

BIN
assets/controls.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

BIN
assets/grass_bottom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

BIN
assets/grass_top.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

BIN
assets/hit.ogg Normal file

Binary file not shown.

BIN
assets/house.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/lose-life.ogg Normal file

Binary file not shown.

BIN
assets/miss.ogg Normal file

Binary file not shown.

BIN
assets/particles.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/plutostardust.ttf Normal file

Binary file not shown.

BIN
assets/projectiles.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
assets/road.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
assets/shoot.ogg Normal file

Binary file not shown.

BIN
assets/sun.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

171
src/assets.rs Normal file
View File

@ -0,0 +1,171 @@
use macroquad::audio::{load_sound_from_bytes, set_sound_volume, Sound};
use macroquad::color::Color;
use macroquad::miniquad::FilterMode;
use macroquad::prelude::{Image, ImageFormat};
use macroquad::text::{
camera_font_scale, draw_text_ex, load_ttf_font_from_bytes, Font, TextParams,
};
use macroquad::texture::Texture2D;
pub struct Assets {
pub blobcat_texture: Texture2D,
pub cloud_texture: Texture2D,
pub sun_texture: Texture2D,
pub grass_top_texture: Texture2D,
pub grass_bottom_texture: Texture2D,
pub road_texture: Texture2D,
pub house_texture: Texture2D,
pub bike_texture: Texture2D,
pub projectiles_texture: Texture2D,
pub particles_texture: Texture2D,
pub menu_texture: Texture2D,
pub controls_texture: Texture2D,
pub miss_sound: Sound,
pub shoot_sound: Sound,
pub hit_sound: Sound,
pub lose_life_sound: Sound,
pub font: Font,
}
impl Assets {
pub async fn load() -> Self {
let blobcat_basic = Image::from_file_with_format(
include_bytes!("../assets/blobcat_basic.png"),
Some(ImageFormat::Png),
);
let blobcat_texture = Texture2D::from_image(&blobcat_basic);
blobcat_texture.set_filter(FilterMode::Nearest);
let cloud = Image::from_file_with_format(
include_bytes!("../assets/cloud1.png"),
Some(ImageFormat::Png),
);
let cloud_texture = Texture2D::from_image(&cloud);
cloud_texture.set_filter(FilterMode::Nearest);
let sun = Image::from_file_with_format(
include_bytes!("../assets/sun.png"),
Some(ImageFormat::Png),
);
let sun_texture = Texture2D::from_image(&sun);
sun_texture.set_filter(FilterMode::Nearest);
let grass_top1 = Image::from_file_with_format(
include_bytes!("../assets/grass_top.png"),
Some(ImageFormat::Png),
);
let grass_top_texture = Texture2D::from_image(&grass_top1);
grass_top_texture.set_filter(FilterMode::Nearest);
let grass_bottom1 = Image::from_file_with_format(
include_bytes!("../assets/grass_bottom.png"),
Some(ImageFormat::Png),
);
let grass_bottom_texture = Texture2D::from_image(&grass_bottom1);
grass_bottom_texture.set_filter(FilterMode::Nearest);
let road = Image::from_file_with_format(
include_bytes!("../assets/road.png"),
Some(ImageFormat::Png),
);
let road_texture = Texture2D::from_image(&road);
road_texture.set_filter(FilterMode::Nearest);
let house = Image::from_file_with_format(
include_bytes!("../assets/house.png"),
Some(ImageFormat::Png),
);
let house_texture = Texture2D::from_image(&house);
house_texture.set_filter(FilterMode::Nearest);
let bike = Image::from_file_with_format(
include_bytes!("../assets/bike.png"),
Some(ImageFormat::Png),
);
let bike_texture = Texture2D::from_image(&bike);
bike_texture.set_filter(FilterMode::Nearest);
let projectiles = Image::from_file_with_format(
include_bytes!("../assets/projectiles.png"),
Some(ImageFormat::Png),
);
let projectiles_texture = Texture2D::from_image(&projectiles);
projectiles_texture.set_filter(FilterMode::Nearest);
let particles = Image::from_file_with_format(
include_bytes!("../assets/particles.png"),
Some(ImageFormat::Png),
);
let particles_texture = Texture2D::from_image(&particles);
particles_texture.set_filter(FilterMode::Nearest);
let menu = Image::from_file_with_format(
include_bytes!("../assets/logo.png"),
Some(ImageFormat::Png),
);
let menu_texture = Texture2D::from_image(&menu);
menu_texture.set_filter(FilterMode::Nearest);
let controls = Image::from_file_with_format(
include_bytes!("../assets/controls.png"),
Some(ImageFormat::Png),
);
let controls_texture = Texture2D::from_image(&controls);
controls_texture.set_filter(FilterMode::Nearest);
let font = load_ttf_font_from_bytes(include_bytes!("../assets/plutostardust.ttf")).unwrap();
let miss_sound = load_sound_from_bytes(include_bytes!("../assets/miss.ogg"))
.await
.unwrap();
let shoot_sound = load_sound_from_bytes(include_bytes!("../assets/shoot.ogg"))
.await
.unwrap();
let hit_sound = load_sound_from_bytes(include_bytes!("../assets/hit.ogg"))
.await
.unwrap();
let lose_life_sound = load_sound_from_bytes(include_bytes!("../assets/lose-life.ogg"))
.await
.unwrap();
Self {
blobcat_texture,
cloud_texture,
sun_texture,
grass_top_texture,
grass_bottom_texture,
road_texture,
house_texture,
bike_texture,
projectiles_texture,
particles_texture,
menu_texture,
controls_texture,
miss_sound,
shoot_sound,
hit_sound,
lose_life_sound,
font,
}
}
}
pub fn draw_string(text: &str, x: f32, y: f32, color: Color, font: Font) {
let (f_size, f_scale, f_aspect) = camera_font_scale(8.0);
draw_text_ex(
text,
x,
y,
TextParams {
font_scale: f_scale,
font_size: f_size,
font_scale_aspect: f_aspect,
font,
color,
..Default::default()
},
)
}

4
src/config.rs Normal file
View File

@ -0,0 +1,4 @@
pub const SCREEN_WIDTH: f32 = 320.0;
pub const SCREEN_HEIGHT: f32 = 180.0;
pub const SCREEN_ASPECT: f32 = SCREEN_WIDTH / SCREEN_HEIGHT;

1091
src/game.rs Normal file

File diff suppressed because it is too large Load Diff

70
src/main.rs Normal file
View File

@ -0,0 +1,70 @@
pub mod assets;
pub mod config;
pub mod game;
mod menu;
pub mod particle;
pub mod sky;
use crate::assets::Assets;
use crate::config::{SCREEN_ASPECT, SCREEN_HEIGHT, SCREEN_WIDTH};
use crate::game::{GameState, GameWorld};
use crate::menu::GameMenu;
use macroquad::audio::AudioContext;
use macroquad::prelude::*;
fn window_conf() -> Conf {
Conf {
window_title: "Blobcat Delivery".parse().unwrap(),
window_width: 1280,
window_height: 720,
..Default::default()
}
}
#[macroquad::main(window_conf)]
async fn main() {
let _audio = AudioContext::new();
let assets = Assets::load().await;
let mut game_state = GameState::Menu(GameMenu::default());
loop {
let sw = screen_width();
let sh = screen_height();
let rr = sw / sh;
let rw = if rr > SCREEN_ASPECT {
SCREEN_ASPECT / rr
} else {
1.0
};
let rh = if rr > SCREEN_ASPECT {
1.0
} else {
rr / SCREEN_ASPECT
};
set_camera(&Camera2D {
zoom: vec2(
1.0 / SCREEN_WIDTH * 2.0 * rw,
1.0 / -SCREEN_HEIGHT * 2.0 * rh,
),
target: vec2(SCREEN_WIDTH / 2.0, SCREEN_HEIGHT / 2.0),
..Default::default()
});
match &mut game_state {
GameState::Game(game) => {
clear_background(SKYBLUE);
game.frame(&assets).await;
}
GameState::Menu(menu) => {
clear_background(DARKBLUE);
if menu.frame(&assets).await {
game_state = GameState::Game(GameWorld::default());
}
}
}
next_frame().await
}
}

135
src/menu.rs Normal file
View File

@ -0,0 +1,135 @@
use crate::assets::{draw_string, Assets};
use crate::config::{SCREEN_HEIGHT, SCREEN_WIDTH};
use crate::sky::Sky;
use macroquad::color;
use macroquad::input::{is_key_pressed, KeyCode};
use macroquad::math::{vec2, Rect, Vec2};
use macroquad::prelude::draw_texture_ex;
use macroquad::texture::{draw_texture, DrawTextureParams};
use macroquad::time::get_frame_time;
struct BlobCat {
pos: Vec2,
velocity: Vec2,
rot: f32,
}
impl Default for BlobCat {
fn default() -> Self {
BlobCat {
pos: vec2(25.0, 100.0),
velocity: vec2(25.0, 25.0),
rot: 0.0,
}
}
}
impl BlobCat {
fn render(&mut self, assets: &Assets) {
if self.pos.x < 0.0 {
self.velocity.x = self.velocity.x.abs();
}
if self.pos.x > SCREEN_WIDTH - 32.0 {
self.velocity.x = -self.velocity.x.abs();
}
if self.pos.y < 0.0 {
self.velocity.y = self.velocity.y.abs();
}
if self.pos.y > SCREEN_HEIGHT - 32.0 {
self.velocity.y = -self.velocity.y.abs();
}
let dt = get_frame_time();
self.pos += self.velocity * dt;
self.rot += dt;
draw_texture_ex(
assets.blobcat_texture,
self.pos.x,
self.pos.y,
color::WHITE,
DrawTextureParams {
rotation: self.rot,
..Default::default()
},
);
}
}
#[derive(Default)]
pub struct GameMenu {
sky: Sky,
blobcat: BlobCat,
}
impl GameMenu {
pub async fn frame(&mut self, assets: &Assets) -> bool {
self.sky.render(assets);
self.blobcat.render(assets);
draw_texture(
assets.menu_texture,
SCREEN_WIDTH / 2.0 - 64.0,
SCREEN_HEIGHT / 2.0 - 95.0,
color::BLACK,
);
draw_texture(
assets.menu_texture,
SCREEN_WIDTH / 2.0 - 64.0,
SCREEN_HEIGHT / 2.0 - 96.0,
color::WHITE,
);
draw_texture_ex(
assets.controls_texture,
SCREEN_WIDTH / 2.0 - 96.0,
SCREEN_HEIGHT / 2.0 - 5.0,
color::WHITE,
DrawTextureParams {
source: Some(Rect::new(0.0, 0.0, 106.0, 32.0)),
..Default::default()
},
);
let move_text = "Movement";
let move_x = SCREEN_WIDTH / 2.0 - 60.0;
let move_y = SCREEN_HEIGHT / 2.0 + 38.0;
draw_string(move_text, move_x, move_y + 0.4, color::BLACK, assets.font);
draw_string(move_text, move_x, move_y, color::WHITE, assets.font);
draw_texture_ex(
assets.controls_texture,
SCREEN_WIDTH / 2.0 + 24.0,
SCREEN_HEIGHT / 2.0 + 13.0,
color::WHITE,
DrawTextureParams {
source: Some(Rect::new(0.0, 32.0, 48.0, 16.0)),
..Default::default()
},
);
let shoot_text = "Shoot";
let shoot_x = SCREEN_WIDTH / 2.0 + 35.0;
let shoot_y = SCREEN_HEIGHT / 2.0 + 38.0;
draw_string(
shoot_text,
shoot_x,
shoot_y + 0.4,
color::BLACK,
assets.font,
);
draw_string(shoot_text, shoot_x, shoot_y, color::WHITE, assets.font);
let play_text = "Press [Space] to play...";
let play_x = SCREEN_WIDTH / 2.0 - 40.0;
let play_y = SCREEN_HEIGHT / 2.0 + 60.0;
draw_string(play_text, play_x, play_y + 0.4, color::BLACK, assets.font);
draw_string(play_text, play_x, play_y, color::WHITE, assets.font);
is_key_pressed(KeyCode::Space)
}
}

363
src/particle.rs Normal file
View File

@ -0,0 +1,363 @@
use crate::assets::Assets;
use macroquad::color;
use macroquad::color::Color;
use macroquad::logging::debug;
use macroquad::math::{vec2, Rect, Vec2};
use macroquad::prelude::DrawTextureParams;
use macroquad::rand::RandomRange;
use macroquad::texture::draw_texture_ex;
use std::ops::Range;
#[derive(Default)]
pub struct ParticleWorld<'a> {
particles: Vec<Particle<'a>>,
}
impl ParticleWorld<'_> {
pub fn spawn<C>(
&mut self,
config: C,
controller: &'_ (impl ParticleController + ParticleSpawner<Config = C>),
) {
controller.spawn(config, |c| self.particles.push(c));
}
pub fn render(&mut self, dt: f32, assets: &Assets) {
self.particles.retain_mut(|particle| {
!matches!(
particle.controller.render(dt, particle, assets),
ParticleLifetime::Destroy
)
})
}
}
#[derive(Copy, Clone)]
pub enum ParticleType {
Spark,
Heart,
Explosion,
Score100,
Score200,
Fail,
}
const SPARK_FRAMES: &[Rect] = &[
Rect {
x: 0.0,
y: 0.0,
w: 8.0,
h: 8.0,
},
Rect {
x: 8.0,
y: 0.0,
w: 8.0,
h: 8.0,
},
Rect {
x: 16.0,
y: 0.0,
w: 8.0,
h: 8.0,
},
Rect {
x: 24.0,
y: 0.0,
w: 8.0,
h: 8.0,
},
];
const HEART_FRAMES: &[Rect] = &[
Rect {
x: 0.0,
y: 8.0,
w: 8.0,
h: 8.0,
},
Rect {
x: 8.0,
y: 8.0,
w: 8.0,
h: 8.0,
},
Rect {
x: 16.0,
y: 8.0,
w: 8.0,
h: 8.0,
},
Rect {
x: 24.0,
y: 8.0,
w: 8.0,
h: 8.0,
},
];
const EXPLOSION_FRAMES: &[Rect] = &[
Rect {
x: 0.0,
y: 16.0,
w: 8.0,
h: 8.0,
},
Rect {
x: 8.0,
y: 16.0,
w: 8.0,
h: 8.0,
},
Rect {
x: 16.0,
y: 16.0,
w: 8.0,
h: 8.0,
},
Rect {
x: 24.0,
y: 16.0,
w: 8.0,
h: 8.0,
},
];
const SCORE100_FRAMES: &[Rect] = &[Rect {
x: 1.0,
y: 25.0,
w: 27.0,
h: 15.0,
}];
const SCORE200_FRAMES: &[Rect] = &[Rect {
x: 33.0,
y: 25.0,
w: 27.0,
h: 15.0,
}];
const FAIL_FRAMES: &[Rect] = &[Rect {
x: 32.0,
y: 9.0,
w: 14.0,
h: 14.0,
}];
impl ParticleType {
fn frames(&self) -> &[Rect] {
match *self {
ParticleType::Spark => SPARK_FRAMES,
ParticleType::Heart => HEART_FRAMES,
ParticleType::Explosion => EXPLOSION_FRAMES,
ParticleType::Score100 => SCORE100_FRAMES,
ParticleType::Score200 => SCORE200_FRAMES,
ParticleType::Fail => FAIL_FRAMES,
}
}
}
#[derive(Copy, Clone, Default)]
pub enum AnimationMode {
#[default]
TieToLifetime,
Repeat(f32),
}
#[derive(Default)]
pub struct ParticleState {
pos: Vec2,
velocity: Vec2,
accel: Vec2,
rot: f32,
rot_velocity: f32,
lifetime: f32,
max_lifetime: f32,
color: Color,
animation_mode: AnimationMode,
scale: f32,
fade_start: f32,
}
pub struct Particle<'a> {
particle_type: ParticleType,
state: ParticleState,
controller: &'a dyn ParticleController,
}
pub enum ParticleLifetime {
Keep,
Destroy,
}
pub trait ParticleSpawner {
type Config;
fn spawn<'a>(&'a self, config: Self::Config, consumer: impl FnMut(Particle<'a>));
}
pub trait ParticleController {
fn render(&self, dt: f32, particle: &mut Particle<'_>, assets: &Assets) -> ParticleLifetime;
}
// Implementations!
fn tick(state: &mut ParticleState, dt: f32) {
state.lifetime += dt;
state.velocity += state.accel * dt;
state.pos += state.velocity * dt;
state.rot += state.rot_velocity * dt;
state.color.a = ((state.fade_start - state.lifetime) / (state.max_lifetime - state.fade_start))
.max(0.0)
.min(1.0)
}
fn render_quad(particle_type: ParticleType, state: &ParticleState, assets: &Assets) {
let sprite_rect = particle_type.frames();
let frame = match state.animation_mode {
AnimationMode::TieToLifetime if state.max_lifetime <= 0.0 => sprite_rect[0],
AnimationMode::TieToLifetime => {
sprite_rect[((state.lifetime / state.max_lifetime) * sprite_rect.len() as f32).floor()
as usize
% sprite_rect.len()]
}
AnimationMode::Repeat(loop_len) => {
sprite_rect[((state.lifetime / loop_len % 1.0) * sprite_rect.len() as f32).floor()
as usize
% sprite_rect.len()]
}
};
draw_texture_ex(
assets.particles_texture,
state.pos.x,
state.pos.y,
state.color,
DrawTextureParams {
dest_size: Some(frame.size() * state.scale),
rotation: state.rot,
source: Some(frame),
..Default::default()
},
);
}
// Ring
#[derive(Clone)]
pub struct ConfigRingBasic {
pub particle_type: ParticleType,
pub pos: Vec2,
pub lifetime: f32,
pub count: usize,
pub radius: Range<f32>,
pub velocity: f32,
pub scale: f32,
pub color: Color,
pub fade_start: f32,
pub angle_cone: Option<Range<f32>>,
}
pub struct ParticleControllerRing;
impl ParticleSpawner for ParticleControllerRing {
type Config = ConfigRingBasic;
fn spawn<'a>(&'a self, config: Self::Config, mut consumer: impl FnMut(Particle<'a>)) {
for _ in 0..config.count {
let angle = if let Some(ref cone) = config.angle_cone {
f32::gen_range(cone.start, cone.end)
} else {
f32::gen_range(0.0, 2.0 * std::f32::consts::PI)
};
let r = f32::gen_range(config.radius.start, config.radius.end);
let cos_t = angle.cos();
let sin_t = -angle.sin();
let x = config.pos.x + cos_t * r;
let y = config.pos.y + sin_t * r;
let vel = vec2(cos_t, sin_t) * config.velocity;
consumer(Particle {
particle_type: config.particle_type,
state: ParticleState {
pos: vec2(x, y),
velocity: vel,
rot: f32::gen_range(0.0, 2.0 * std::f32::consts::PI),
color: config.color,
max_lifetime: config.lifetime,
scale: config.scale,
fade_start: config.fade_start,
..Default::default()
},
controller: self,
});
}
}
}
impl ParticleController for ParticleControllerRing {
fn render(&self, dt: f32, particle: &mut Particle<'_>, assets: &Assets) -> ParticleLifetime {
tick(&mut particle.state, dt);
if particle.state.lifetime > particle.state.max_lifetime {
ParticleLifetime::Destroy
} else {
render_quad(particle.particle_type, &particle.state, assets);
ParticleLifetime::Keep
}
}
}
// PopUp
#[derive(Clone)]
pub struct ConfigPopUpBasic {
pub particle_type: ParticleType,
pub pos: Vec2,
pub lifetime: f32,
pub velocity: Vec2,
pub scale: f32,
pub color: Color,
pub fade_start: f32,
pub accel: Vec2,
}
pub struct ParticleControllerPopUp;
impl ParticleSpawner for ParticleControllerPopUp {
type Config = ConfigPopUpBasic;
fn spawn<'a>(&'a self, config: Self::Config, mut consumer: impl FnMut(Particle<'a>)) {
consumer(Particle {
particle_type: config.particle_type,
state: ParticleState {
pos: config.pos,
velocity: config.velocity,
color: config.color,
max_lifetime: config.lifetime,
scale: config.scale,
fade_start: config.fade_start,
accel: config.accel,
..Default::default()
},
controller: self,
});
}
}
impl ParticleController for ParticleControllerPopUp {
fn render(&self, dt: f32, particle: &mut Particle<'_>, assets: &Assets) -> ParticleLifetime {
tick(&mut particle.state, dt);
if particle.state.lifetime > particle.state.max_lifetime {
ParticleLifetime::Destroy
} else {
render_quad(particle.particle_type, &particle.state, assets);
ParticleLifetime::Keep
}
}
}

88
src/sky.rs Normal file
View File

@ -0,0 +1,88 @@
use crate::assets::Assets;
use crate::config::{SCREEN_HEIGHT, SCREEN_WIDTH};
use macroquad::color;
use macroquad::color::Color;
use macroquad::math::Vec2;
use macroquad::prelude::{draw_texture, draw_texture_ex, get_frame_time, DrawTextureParams};
use macroquad::rand::RandomRange;
pub struct Sky {
sun: Sun,
clouds: Vec<Cloud>,
}
impl Default for Sky {
fn default() -> Self {
let sun = Sun {
rot: -core::f32::consts::FRAC_PI_6,
pos: Vec2::new(25.0, 25.0),
};
let mut clouds = Vec::new();
const CLOUD_CNT: usize = 20;
for i in 0..CLOUD_CNT {
let pos_x = f32::gen_range(0.0, SCREEN_WIDTH);
let pos_y = f32::gen_range(0.0, 16384.0);
let speed = f32::gen_range(5.0, 15.0);
clouds.push(Cloud {
pos: Vec2::new(pos_x, pos_y),
speed,
shade: 0.75 + 0.25 * i as f32 / CLOUD_CNT as f32,
})
}
Self { sun, clouds }
}
}
impl Sky {
pub fn render(&mut self, assets: &Assets) {
self.sun.draw(assets);
self.clouds.iter_mut().for_each(|cloud| cloud.draw(assets));
}
}
pub struct Sun {
pub pos: Vec2,
pub rot: f32,
}
impl Sun {
pub fn draw(&self, assets: &Assets) {
draw_texture_ex(
assets.sun_texture,
self.pos.x,
self.pos.y,
color::WHITE,
DrawTextureParams {
rotation: self.rot,
..Default::default()
},
);
}
}
#[derive(Debug)]
pub struct Cloud {
pos: Vec2,
speed: f32,
shade: f32,
}
impl Cloud {
pub fn draw(&mut self, assets: &Assets) {
self.pos.x -= self.speed * get_frame_time();
let w = assets.cloud_texture.width();
let h = assets.cloud_texture.height();
draw_texture(
assets.cloud_texture,
self.pos.x.rem_euclid(SCREEN_WIDTH + w * 4.0) - w * 2.0,
self.pos.y.rem_euclid(SCREEN_HEIGHT / 3.0 - h),
Color::new(self.shade, self.shade, 1.0, 1.0),
);
}
}