Browse Source

working game

main
cynthia 3 years ago
parent
commit
3e8bb4f294
  1. BIN
      assets/fira_code.ttf
  2. BIN
      html/assets/fira_code.ttf
  3. 27
      html/fishing-minigame.html
  4. BIN
      html/fishing-minigame.wasm
  5. 1
      html/mq_js_bundle.js
  6. 75
      src/grammar.rs
  7. 667
      src/main.rs

BIN
assets/fira_code.ttf

BIN
html/assets/fira_code.ttf

27
html/fishing-minigame.html

@ -0,0 +1,27 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>TITLE</title>
<style>
canvas {
margin: 0px;
padding: 0px;
width: 720px;
height: 425px;
overflow: hidden;
/* position: absolute; */
background: black;
z-index: 0;
}
</style>
</head>
<body>
<canvas id="glcanvas" tabindex='1'></canvas>
<!-- self-hosted version of https://github.com/not-fl3/macroquad/blob/master/js/mq_js_bundle.js -->
<script src="./mq_js_bundle.js"></script>
<script>load("fishing-minigame.wasm");</script> <!-- Your compiled wasm file -->
</body>
</html>

BIN
html/fishing-minigame.wasm

1
html/mq_js_bundle.js
File diff suppressed because it is too large
View File

75
src/grammar.rs

@ -0,0 +1,75 @@
// minimal ripoff of tracery by kate compton (tracery.io)
use std::collections::BTreeMap;
use macroquad::rand::*;
pub struct Grammar {
pub grammar: BTreeMap<String, Vec<String>>,
}
impl Grammar {
pub fn new() -> Self {
let mut grammar = BTreeMap::new();
grammar.insert(String::from("origin"), vec![]);
Self { grammar }
}
pub fn from(rules: impl IntoIterator<Item = (String, Vec<String>)>) -> Self {
let mut grammar = BTreeMap::new();
for (token, rule) in rules {
grammar.insert(token, rule);
}
Self { grammar }
}
pub fn insert(&mut self, key: String, val: String) {
let entry = self.grammar.entry(key).or_insert_with(Vec::new);
entry.push(val);
}
pub fn flatten(&self) -> Option<String> {
self.flatten_with_rule(&"origin")
}
pub fn flatten_with_rule(&self, rule: &str) -> Option<String> {
let rule = self.grammar.get(rule)?;
if rule.is_empty() {
return None;
}
let idx = gen_range(0, rule.len());
let rule = &rule[idx];
let mut output = String::from("");
for (i, piece) in rule.split('#').enumerate() {
if i % 2 == 0 {
output += piece;
} else {
output += &self
.flatten_with_rule(&piece)
.unwrap_or_else(|| String::from(piece));
}
}
Some(output)
}
}
#[cfg(test)]
mod test {
use super::*;
use macroquad::rand::srand;
#[test]
fn test_grammar() -> Result<(), ()> {
use std::time::*;
let now = Instant::now();
std::thread::sleep(Duration::from_millis(150));
srand(now.elapsed().as_secs_f64().to_bits());
let mut grammar = Grammar::new();
grammar.insert("origin".into(), "egg".into());
grammar.insert("origin".into(), "sandwich".into());
grammar.insert("origin".into(), "#sandwich#".into());
grammar.insert("sandwich".into(), "i love sandwiches".into());
let output = grammar.flatten().ok_or(())?;
assert!(output == "egg" || output == "sandwich" || output == "i love sandwiches");
Ok(())
}
}

667
src/main.rs

@ -1,41 +1,142 @@
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::new_without_default)]
#![allow(unused)]
use macroquad::prelude::*;
use macroquad::rand::*;
const FISH_PARTS_RIGHT: (&str, &str) = ("><", ">");
const FISH_PARTS_LEFT: (&str, &str) = ("<", "><");
const WATER_LEVEL: i32 = 100;
const WIDTH: i32 = 780;
const FISH_ZONE: i32 = 500;
const FONT_SIZE: f32 = 20.0;
const ANIM_CHANGE: u32 = 150; // in milliseconds
pub mod grammar;
use grammar::*;
const FONT: &str = &"./assets/fira_code.ttf";
const FONT_WIDTH: u16 = 8;
const FONT_HEIGHT_REAL: f32 = FONT_WIDTH as f32 / 8.0 * 13.0;
const FONT_HEIGHT: u16 = (FONT_HEIGHT_REAL + 4.0) as u16;
const FONT_DIFFERENCE: u16 = FONT_HEIGHT - FONT_HEIGHT_REAL as u16;
const GRID_WIDTH: usize = 60;
const GRID_HEIGHT: usize = 25;
// 9 = 3 + height of fishing guy - 1 for some reason
const WATER_LEVEL: i32 = 9;
const HEIGHT: i32 = FONT_HEIGHT as i32 * 25;
// 90 = 60 + 30
const WIDTH: i32 = FONT_WIDTH as i32 * 90;
const UPDATE: f64 = 150.0 / 1000.0; // in seconds
const ANIM_CHANGE: f64 = 750.0 / 1000.0;
fn window_conf() -> Conf {
Conf {
window_title: "fishing minigame".to_owned(),
window_width: WIDTH,
window_height: FISH_ZONE,
window_height: HEIGHT,
fullscreen: false,
..Default::default()
}
}
struct Fish {
is_right: bool,
// middle length
length: u8,
pos: Vec2,
pos: IVec2,
}
impl Fish {
fn new() -> Self {
let is_right = gen_range(0, 2) == 0;
let length = gen_range(0, 6);
Self {
is_right,
length,
pos: IVec2::new(
if is_right {
-(length as i32 + 3)
} else {
GRID_WIDTH as i32
},
gen_range(WATER_LEVEL + 1, GRID_HEIGHT as i32 - 2),
),
}
}
fn into_text(self, grammar: &Grammar) -> Option<String> {
grammar.flatten()
}
}
enum Mode {
Start,
Play,
End,
}
struct Game {
mode: Mode,
state: State,
draw: Draw,
grammar: Grammar,
}
impl Game {
fn new() -> Self {
Self {
state: State::new(),
draw: Draw::new(),
async fn new() -> Result<Self, FontError> {
let mut draw = Draw::new().await.unwrap();
let mut state = State::new();
game_setup(&mut state, &mut draw);
let grammar = grammar_setup();
Ok(Self {
mode: Mode::Start,
state,
draw,
grammar,
})
}
fn update(&mut self) {
// fish update
let mut remove = Vec::new();
for (i, fish) in self.state.fish.iter_mut().enumerate() {
// if something is hooked and it's not this
let is_hooked = self.state.is_hooked.map(|idx| idx == i).unwrap_or(false);
if !is_hooked {
if fish.is_right {
fish.pos.x += 1;
if fish.pos.x >= GRID_WIDTH as i32 {
remove.push(i);
}
} else {
fish.pos.x -= 1;
if fish.pos.x <= -(fish.length as i32 + 3) {
remove.push(i);
}
}
}
}
for i in remove.into_iter().rev() {
self.state.fish.remove(i);
if let Some(idx) = &mut self.state.is_hooked {
if *idx > i {
*idx -= 1;
}
}
}
if self.state.fish.len() < 3 && gen_range(0, 15) < 1 {
self.state.fish.push(Fish::new());
}
// collision
let hook = self.state.hook;
if self.state.is_hooked.is_none() {
for (i, fish) in self.state.fish.iter().enumerate() {
if fish.pos.y == hook.y
&& fish.pos.x <= hook.x
&& fish.pos.x + fish.length as i32 + 3 > hook.x
{
self.state.is_hooked = Some(i);
break;
}
}
}
}
}
@ -43,16 +144,78 @@ impl Game {
struct Draw {
// only two frames of animation
first_frame: bool,
frame_counter: usize,
// animated sprites
animations: Vec<Animation>,
// static sprites
sprites: Vec<Sprite>,
font: Font,
}
impl Draw {
fn new() -> Self {
Self {
async fn new() -> Result<Self, FontError> {
// font: fira code. dimensions: 13.0x8.0
let font = load_ttf_font(FONT).await?;
Ok(Self {
first_frame: true,
frame_counter: 0,
animations: Vec::new(),
sprites: Vec::new(),
font,
})
}
fn add_sprite(&mut self, sprite: Sprite) -> usize {
self.sprites.push(sprite);
self.sprites.len() - 1
}
fn add_animation(&mut self, animation: Animation) -> usize {
self.animations.push(animation);
self.animations.len() - 1
}
}
struct Sprite {
width: usize,
height: usize,
color: Option<Color>,
sprite: String,
}
impl Sprite {
fn new(width: usize, height: usize, color: Option<Color>, sprite: String) -> Self {
assert_eq!(width * height, sprite.len() - height + 1);
Self {
width,
height,
color,
sprite,
}
}
fn draw(&self, pos: IVec2, font: Font) {
let pos = pos.as_f32() * Vec2::new(FONT_WIDTH as f32, FONT_HEIGHT as f32);
if let Some(color) = self.color {
draw_rectangle(
pos.x,
pos.y,
self.width as f32 * FONT_WIDTH as f32,
self.height as f32 * FONT_HEIGHT as f32,
color,
);
}
for (i, row) in self.sprite.split('\n').enumerate() {
let i = (i + 1) as f32 * FONT_HEIGHT as f32 - FONT_DIFFERENCE as f32;
draw_text_ex(
row,
pos.x,
pos.y + i,
TextParams {
font,
color: BLACK,
font_size: FONT_HEIGHT - FONT_DIFFERENCE,
..Default::default()
},
);
}
}
}
@ -61,13 +224,14 @@ struct Animation {
width: usize,
height: usize,
color: Color,
/// animations can have two frames and must be strings
frames: [String; 2],
}
impl Animation {
fn new(width: usize, height: usize, color: Color, frames: [String; 2]) -> Self {
assert_eq!(width * height, frames[0].len());
assert_eq!(width * height, frames[1].len());
assert_eq!(width * height, frames[0].len() - height + 1);
assert_eq!(width * height, frames[1].len() - height + 1);
Self {
width,
height,
@ -76,28 +240,58 @@ impl Animation {
}
}
fn draw(&self, pos: Vec2) {
fn draw(&self, pos: IVec2, first_frame: bool, font: Font) {
let pos = pos.as_f32() * Vec2::new(FONT_WIDTH as f32, FONT_HEIGHT as f32);
draw_rectangle(
pos.x,
pos.y,
self.width as f32 * FONT_SIZE,
self.height as f32 * FONT_SIZE,
self.width as f32 * FONT_WIDTH as f32,
self.height as f32 * FONT_HEIGHT as f32,
self.color,
);
for (i, row) in self.frames[first_frame as usize].split('\n').enumerate() {
let i = (i + 1) as f32 * FONT_HEIGHT as f32 - FONT_DIFFERENCE as f32;
draw_text_ex(
row,
pos.x,
pos.y + i,
TextParams {
font,
color: BLACK,
font_size: FONT_HEIGHT - FONT_DIFFERENCE,
..Default::default()
},
);
}
}
}
struct State {
// TODO: consider vecdeque instead of stack
fish: Vec<Fish>,
player: Vec2,
hook: IVec2,
// which fish is hooked
is_hooked: Option<usize>,
// positions of other things (unmoving)
positions: Vec<IVec2>,
// parallel vec of sprites corresponding to ^
sprites: Vec<DrawType>,
caught_fish: Vec<String>,
}
enum DrawType {
Anim(usize),
Sprite(usize),
}
impl State {
fn new() -> Self {
State {
fish: Vec::new(),
player: Vec2::new(FISH_ZONE as f32 / 2.0, WATER_LEVEL as f32),
hook: IVec2::new(GRID_WIDTH as i32 / 2, WATER_LEVEL + 5),
is_hooked: None,
positions: Vec::new(),
sprites: Vec::new(),
caught_fish: Vec::new(),
}
}
@ -105,27 +299,418 @@ impl State {
#[macroquad::main(window_conf)]
async fn main() {
let mut game = Game::new();
srand(get_time().to_bits());
let mut game = Game::new().await.unwrap();
let mut updated = get_time();
let mut anim_updated = get_time();
loop {
if is_key_down(KeyCode::Escape) {
break;
match game.mode {
Mode::Start => {
// draw
clear_background(SKYBLUE);
draw_text_centered(
&"press ENTER to start",
HEIGHT as f32 / 2.0 - FONT_HEIGHT as f32 * 2.0,
game.draw.font,
);
draw_text_centered(
&"use up and down to move the hook",
HEIGHT as f32 / 2.0 - FONT_HEIGHT as f32,
game.draw.font,
);
draw_text_centered(
&"use space to catch a fish",
HEIGHT as f32 / 2.0,
game.draw.font,
);
draw_text_centered(
&"use escape to end the game",
HEIGHT as f32 / 2.0 + FONT_HEIGHT as f32,
game.draw.font,
);
if is_key_pressed(KeyCode::Enter) {
game.mode = Mode::Play;
}
}
Mode::Play => {
if is_key_pressed(KeyCode::Escape) {
game.mode = Mode::End;
continue;
}
if is_key_pressed(KeyCode::Up) {
game.state.hook.y = (game.state.hook.y - 1).max(WATER_LEVEL);
}
if is_key_pressed(KeyCode::Down) {
game.state.hook.y = (game.state.hook.y + 1).min(GRID_HEIGHT as i32 - 2);
}
if let Some(idx) = game.state.is_hooked {
game.state.fish[idx].pos.y = game.state.hook.y;
if is_key_pressed(KeyCode::Space) {
let fish = game.state.fish.remove(idx);
game.state
.caught_fish
.push(fish.into_text(&game.grammar).unwrap());
game.state.is_hooked = None;
}
}
if get_time() - updated >= UPDATE {
game.update();
updated = get_time();
}
game.draw();
if get_time() - anim_updated >= ANIM_CHANGE {
game.draw.first_frame = !game.draw.first_frame;
anim_updated = get_time();
}
}
Mode::End => {
clear_background(SKYBLUE);
draw_text_centered(
&format!(
"you caught {} fish, thanks for playing",
game.state.caught_fish.len()
),
HEIGHT as f32 / 2.0,
game.draw.font,
);
draw_text_centered(
&"press ENTER to restart",
HEIGHT as f32 / 2.0 + FONT_HEIGHT as f32,
game.draw.font,
);
if is_key_pressed(KeyCode::Enter) {
game.state = State::new();
game.draw.animations.clear();
game.draw.sprites.clear();
game_setup(&mut game.state, &mut game.draw);
game.mode = Mode::Start;
}
}
}
draw(&game);
next_frame().await;
}
}
fn draw(game: &Game) {
const FZ: f32 = FISH_ZONE as f32;
const WL: f32 = WATER_LEVEL as f32;
clear_background(SKYBLUE);
// sky
draw_rectangle(0.0, 0.0, FZ, WL, WHITE);
// ground
draw_rectangle(0.0, FZ - 30.0, FZ, 30.0, BEIGE);
// static sprites
// animated stuff
impl Game {
fn draw(&self) {
const FISH_WIDTH: f32 = FONT_WIDTH as f32 * GRID_WIDTH as f32;
const H: f32 = HEIGHT as f32;
const WL: f32 = WATER_LEVEL as f32 * FONT_HEIGHT as f32;
// water
clear_background(SKYBLUE);
// sky
draw_rectangle(0.0, 0.0, FISH_WIDTH, WL, WHITE);
// ground
draw_rectangle(
0.0,
H - FONT_HEIGHT as f32,
FISH_WIDTH,
FONT_HEIGHT as f32,
BEIGE,
);
// static sprites
for (pos, sprite) in self.state.positions.iter().zip(self.state.sprites.iter()) {
match sprite {
DrawType::Anim(i) => {
self.draw.animations[*i].draw(*pos, self.draw.first_frame, self.draw.font)
}
DrawType::Sprite(i) => self.draw.sprites[*i].draw(*pos, self.draw.font),
}
}
// fish
for fish in self.state.fish.iter() {
let sprite = &self.draw.sprites[fish.length as usize * 2 + fish.is_right as usize];
sprite.draw(fish.pos, self.draw.font);
}
// hook
self.draw.sprites[12].draw(self.state.hook, self.draw.font);
// line
let sprite = (0..self.state.hook.y - 5)
.map(|_| "|\n")
.collect::<String>();
let sprite = sprite.trim().to_string();
let sprite = Sprite::new(1, self.state.hook.y as usize - 5, None, sprite);
sprite.draw(IVec2::new(GRID_WIDTH as i32 / 2, 5), self.draw.font);
// sidebar
draw_rectangle(FISH_WIDTH, 0.0, WIDTH as f32 - FISH_WIDTH, H, LIGHTGRAY);
// draw fish info
// sidebar width: 480 - 8px? margins * 2 = 464 / 16 = 29 characters / line
let mut y = 30.0;
let x = GRID_WIDTH as f32 * FONT_WIDTH as f32 + 8.0;
for text in self.state.caught_fish.iter().rev() {
if y > HEIGHT as f32 + FONT_HEIGHT as f32 {
break;
}
y += 20.0;
let mut line = "".to_string();
line.reserve_exact(28);
for word in text.split_whitespace() {
if line.len() + word.len() > 28 {
draw_text_ex(
&line,
x,
y,
TextParams {
font: self.draw.font,
color: BLACK,
font_size: FONT_HEIGHT - FONT_DIFFERENCE,
..Default::default()
},
);
line.clear();
y += FONT_HEIGHT as f32;
}
line += word;
line += " ";
}
draw_text_ex(
&line,
x,
y,
TextParams {
font: self.draw.font,
color: BLACK,
font_size: FONT_HEIGHT - FONT_DIFFERENCE,
..Default::default()
},
);
y += FONT_HEIGHT as f32;
}
}
}
fn grammar_setup() -> Grammar {
let rules = [
("origin".to_string(),
vec!["#fish#. #fishFact#".to_string(),
"#fish#. #fishFact#".to_string(),
"#fish#. #fishFact#".to_string(),
"#fish#. #fishFact#".to_string(),
"#fish#. #fishFact#".to_string(),
"#fish#. #fishFact#".to_string(),
"#fish#. #fishFact#".to_string(),
"#fish#. #fishFact#".to_string(),
"#fish#. #fishFact#".to_string(),
"#seaFish#. #seaFishFact#".to_string(),
"#largeFish#. #largeFishFact#".to_string(),
"#whale#. #whaleFact#".to_string(),
"#unnervingFish#. #unnervingFishFact#".to_string(),
"sea bass. no, wait--- it's at least a c+!".to_string()]),
("fish".to_string(),
vec!["rainbow trout".to_string(),
"salmon".to_string(),
"black bass".to_string(),
"catfish".to_string(),
"brook trout".to_string(),
"cod".to_string(),
"minnow".to_string(),
"turbofish".to_string()]),
("fishFact".to_string(),
vec!["this fish loves to swim about.".to_string(),
"this fish was sleeping and you woke it up, which is kind of rude.".to_string(),
"this fish will grant you three wishes if you don't eat it but you weren't going to do that anyway.".to_string(),
"fish are neat :)".to_string(),
"this fish likes to hide under rocks.".to_string(),
"this fish can breathe underwater.".to_string(),
"this fish likes to blow bubbles.".to_string(),
"this fish likes to flick its tail.".to_string(),
"this fish can do a backflip.".to_string(),
"this fish is very tired and wants to rest.".to_string(),
"this fish wants to show you some sick card tricks.".to_string()]),
("unnervingFish".to_string(),
vec!["swamp eel".to_string(),
"pike".to_string(),
"jellyfish".to_string(),
"sunfish".to_string(),
"angler fish".to_string(),
"squid".to_string()]),
("unnervingFishFact".to_string(),
vec!["#fishFact#".to_string(),
"i'm spooked by this fish.".to_string(),
"this fish has so many teeth.".to_string(),
"you feel like this fish will haunt your nightmares.".to_string(),
"please put it back.".to_string()]),
("largeFish".to_string(),
vec!["lemon shark".to_string(),
"mako shark".to_string(),
"whale shark".to_string(),
"great white shark".to_string()]),
("largeFishFact".to_string(),
vec!["#seaFishFact#".to_string(),
"#fishFact#".to_string(),
"this fish is incredibly large.".to_string(),
"OH GOD IT'S JUST SO BIG".to_string(),
"this fish might eat you? you're not sure, it hasn't happened yet".to_string(),
"this fish has so many teeth.".to_string()]),
("whale".to_string(),
vec!["blue whale".to_string(),
"humpback whale".to_string(),
"sperm whale".to_string(),
"right whale".to_string(),
"razor back whale".to_string()]),
("whaleFact".to_string(),
vec!["#seaFishFact#".to_string(),
"#largeFishFact#".to_string(),
"\"Oh, man! Admire and model thyself after the whale! Do thou, too, remain warm among ice. Do thou, too, live in this world without being of it. Be cool at the equator; keep thy blood fluid at the pole. Like the great dome of St. Peter's, and like the great whale, retain, O man! in all seasons a temperature of thine own.\" (melville 343)".to_string(),
"\"I. A Fast-Fish belongs to the party fast to it. II. A Loose-Fish is fair game for anybody who can soonest catch it.\" (melville 441)".to_string(),
"\"All men live enveloped in whale lines.\" (melville 315)".to_string(),
"\"Let him go. I know little more of him,
nor does anybody else.\" (melville 153)".to_string(),
"\"a whale is A SPOUTING FISH WITH A HORIZONTAL TAIL. There you have him.\" (melville 148)".to_string()]),
("seaFish".to_string(),
vec!["black sea bass".to_string(),
"mackerel".to_string(),
"tuna".to_string(),
"sea bass".to_string(),
"anchovy".to_string()]),
("seaFishFact".to_string(),
vec!["#fishFact#".to_string(),
"this fish lives in the sea and you're not sure how it got here.".to_string(),
"you're not even sure if this fish can live in freshwater, but okay.".to_string()])
];
Grammar::from(rules)
}
fn game_setup(state: &mut State, draw: &mut Draw) {
// fish
// sidebar
draw_rectangle(FZ, 0.0, WIDTH as f32 - FZ, FZ, LIGHTGRAY);
for i in 0..6 {
let mut middle = "".to_string();
middle.reserve_exact(i);
for _ in 0..i {
middle += ":";
}
draw.add_sprite(Sprite::new(
i + 3,
1,
Some(SKYBLUE),
"<".to_owned() + &middle.clone() + "><",
));
draw.add_sprite(Sprite::new(
i + 3,
1,
Some(SKYBLUE),
"><".to_owned() + &middle + ">",
));
}
// hook
draw.add_sprite(Sprite::new(1, 1, Some(SKYBLUE), "&".to_owned()));
// seaweed
let mut plant_already = [false; GRID_WIDTH];
for _ in 0..5 {
let mut x = gen_range(1, GRID_WIDTH - 1);
while plant_already[x] {
x = gen_range(1, GRID_WIDTH - 1);
}
plant_already[x] = true;
let height = gen_range(2, (GRID_HEIGHT - WATER_LEVEL as usize) / 2);
let mut plant = "".to_string();
plant.reserve_exact(height + 2);
for _ in 0..height / 2 + 1 {
plant += "\\\n/\n";
}
let start = gen_range(0, 2) == 0;
let anim = if start {
[
plant[..height * 2 - 1].to_string(),
plant[2..height * 2 + 1].to_string(),
]
} else {
[
plant[2..height * 2 + 1].to_string(),
plant[..height * 2 - 1].to_string(),
]
};
let anim = Animation::new(1, height, GREEN, anim);
let i = draw.add_animation(anim);
state
.positions
.push(IVec2::new(x as i32, GRID_HEIGHT as i32 - height as i32 - 1));
state.sprites.push(DrawType::Anim(i));
}
// water
let mut water = "^".to_string();
water.reserve_exact(GRID_WIDTH);
for _ in 0..GRID_WIDTH / 2 {
water += "~^";
}
let anim = Animation::new(
GRID_WIDTH,
1,
SKYBLUE,
[water[..GRID_WIDTH].to_string(), water[1..].to_string()],
);
let i = draw.add_animation(anim);
state.positions.push(IVec2::new(0, WATER_LEVEL));
state.sprites.push(DrawType::Anim(i));
// ground
let groud_chars = [
"-", "=", ".", "__", "--", "==", "..", "___", "---", "===", "...",
];
let mut ground = "".to_string();
let mut i = 0;
while i < GRID_WIDTH {
let add = groud_chars[gen_range(0, groud_chars.len())];
i += add.len();
ground += add;
}
ground = ground[..GRID_WIDTH].to_string();
let i = draw.add_sprite(Sprite::new(GRID_WIDTH, 1, Some(BEIGE), ground));
state.positions.push(IVec2::new(0, GRID_HEIGHT as i32 - 1));
state.sprites.push(DrawType::Sprite(i));
// water
// dude
#[rustfmt::skip]
let dude = r#" O
|--o_______________________.
|__
===== |
| | |
| | "#;
let i = draw.add_sprite(Sprite::new(31, 6, None, dude.to_string()));
state.positions.push(IVec2::new(0, 3));
state.sprites.push(DrawType::Sprite(i));
}
fn draw_text_centered(text: &str, y: f32, font: Font) {
let dims = measure_text(text, Some(font), FONT_HEIGHT - FONT_DIFFERENCE, 1.0);
draw_text_ex(
text,
WIDTH as f32 / 2.0 - dims.width / 2.0,
y,
TextParams {
font,
color: BLACK,
font_size: FONT_HEIGHT - FONT_DIFFERENCE,
..Default::default()
},
);
}
Loading…
Cancel
Save