Add a warning when low on time or blood sugar

This commit is contained in:
Dustin Carlino 2020-12-17 15:50:29 -08:00
parent 57ea1b898d
commit 631b164cf8
4 changed files with 123 additions and 9 deletions

View File

@ -2,17 +2,19 @@ use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng; use rand_xorshift::XorShiftRng;
use geom::{Distance, Duration, PolyLine, Pt2D, Time}; use geom::{Distance, Duration, PolyLine, Pt2D, Time};
use widgetry::{Color, Drawable, EventCtx, GeomBatch, GfxCtx}; use widgetry::{Color, Drawable, EventCtx, GeomBatch, GfxCtx, RewriteColor};
pub struct Animator { pub struct Animator {
active: Vec<Animation>, active: Vec<Animation>,
draw_current: Drawable, draw_mapspace: Drawable,
draw_screenspace: Option<Drawable>,
} }
struct Animation { struct Animation {
start: Time, start: Time,
end: Time, end: Time,
effect: Effect, effect: Effect,
screenspace: bool,
} }
pub enum Effect { pub enum Effect {
@ -26,13 +28,19 @@ pub enum Effect {
width: Distance, width: Distance,
pl: PolyLine, pl: PolyLine,
}, },
Flash {
orig: GeomBatch,
alpha_scale: (f32, f32),
cycles: usize,
},
} }
impl Animator { impl Animator {
pub fn new(ctx: &EventCtx) -> Animator { pub fn new(ctx: &EventCtx) -> Animator {
Animator { Animator {
active: Vec::new(), active: Vec::new(),
draw_current: Drawable::empty(ctx), draw_mapspace: Drawable::empty(ctx),
draw_screenspace: None,
} }
} }
@ -42,6 +50,16 @@ impl Animator {
start: now, start: now,
end: now + duration, end: now + duration,
effect, effect,
screenspace: false,
});
}
pub fn add_screenspace(&mut self, now: Time, duration: Duration, effect: Effect) {
self.active.push(Animation {
start: now,
end: now + duration,
effect,
screenspace: true,
}); });
} }
@ -49,7 +67,8 @@ impl Animator {
if self.active.is_empty() { if self.active.is_empty() {
return; return;
} }
let mut batch = GeomBatch::new(); let mut mapspace = GeomBatch::new();
let mut screenspace = GeomBatch::new();
self.active.retain(|anim| { self.active.retain(|anim| {
let pct = (now - anim.start) / (anim.end - anim.start); let pct = (now - anim.start) / (anim.end - anim.start);
if pct < 0.0 { if pct < 0.0 {
@ -58,15 +77,29 @@ impl Animator {
} else if pct > 1.0 { } else if pct > 1.0 {
false false
} else { } else {
anim.effect.render(pct, &mut batch); if anim.screenspace {
anim.effect.render(pct, &mut screenspace);
} else {
anim.effect.render(pct, &mut mapspace);
}
true true
} }
}); });
self.draw_current = ctx.upload(batch); self.draw_mapspace = ctx.upload(mapspace);
if screenspace.is_empty() {
self.draw_screenspace = None;
} else {
self.draw_screenspace = Some(ctx.upload(screenspace));
}
} }
pub fn draw(&self, g: &mut GfxCtx) { pub fn draw(&self, g: &mut GfxCtx) {
g.redraw(&self.draw_current); g.redraw(&self.draw_mapspace);
if let Some(ref d) = self.draw_screenspace {
g.fork_screenspace();
g.redraw(d);
g.unfork();
}
} }
pub fn is_done(&self) -> bool { pub fn is_done(&self) -> bool {
@ -94,6 +127,19 @@ impl Effect {
batch.push(*color, pl.make_polygons(*width)); batch.push(*color, pl.make_polygons(*width));
} }
} }
Effect::Flash {
ref orig,
alpha_scale,
cycles,
} => {
// -1 to 1
let shift = (pct * (*cycles as f64) * (2.0 * std::f64::consts::PI)).sin() as f32;
let midpt = (alpha_scale.0 + alpha_scale.1) / 2.0;
let half_range = (alpha_scale.1 - alpha_scale.0) / 2.0;
let alpha = midpt + shift * half_range;
batch.append(orig.clone().color(RewriteColor::ChangeAlpha(alpha)));
}
} }
} }
} }

View File

@ -255,6 +255,7 @@ impl Game {
let refill = self.state.vehicle.max_energy - self.state.energy; let refill = self.state.vehicle.max_energy - self.state.energy;
if refill > 0 { if refill > 0 {
self.state.energy += refill; self.state.energy += refill;
self.state.warned_low_energy = false;
let path_speed = Duration::seconds(0.2); let path_speed = Duration::seconds(0.2);
self.animator.add( self.animator.add(
app.time, app.time,
@ -387,6 +388,69 @@ impl State<App> for Game {
); );
} }
if !self.state.warned_low_time
&& self.state.level.time_limit - (app.time - Time::START_OF_DAY)
<= Duration::seconds(20.0)
{
self.state.warned_low_time = true;
self.animator.add(
app.time,
Duration::seconds(2.0),
Effect::Flash {
alpha_scale: (0.1, 0.5),
cycles: 2,
orig: GeomBatch::from(vec![(
Color::RED,
app.map.get_boundary_polygon().clone(),
)]),
},
);
self.animator.add_screenspace(
app.time,
Duration::seconds(2.0),
Effect::Scale {
lerp_scale: (1.0, 4.0),
center: {
let pt = ctx.canvas.center_to_screen_pt();
Pt2D::new(pt.x, pt.y / 2.0)
},
orig: Text::from(Line("Almost out of time!"))
.bg(Color::RED)
.render_autocropped(ctx),
},
);
}
if !self.state.warned_low_energy && self.state.energy < 30 {
self.state.warned_low_energy = true;
self.animator.add(
app.time,
Duration::seconds(2.0),
Effect::Flash {
alpha_scale: (0.1, 0.5),
cycles: 2,
orig: GeomBatch::from(vec![(
Color::RED,
app.map.get_boundary_polygon().clone(),
)]),
},
);
self.animator.add_screenspace(
app.time,
Duration::seconds(2.0),
Effect::Scale {
lerp_scale: (1.0, 4.0),
center: {
let pt = ctx.canvas.center_to_screen_pt();
Pt2D::new(pt.x, pt.y / 2.0)
},
orig: Text::from(Line("Low on blood sugar, refill soon!"))
.bg(Color::RED)
.render_autocropped(ctx),
},
);
}
ctx.request_update(UpdateType::Game); ctx.request_update(UpdateType::Game);
return Transition::Keep; return Transition::Keep;
} }
@ -504,6 +568,8 @@ struct GameState {
idle_time: Duration, idle_time: Duration,
game_over: bool, game_over: bool,
warned_low_time: bool,
warned_low_energy: bool,
} }
impl GameState { impl GameState {
@ -524,6 +590,8 @@ impl GameState {
idle_time: Duration::ZERO, idle_time: Duration::ZERO,
game_over: false, game_over: false,
warned_low_time: false,
warned_low_energy: false,
} }
} }

View File

@ -19,7 +19,7 @@ impl Vehicle {
name: "bike".to_string(), name: "bike".to_string(),
speed: Speed::miles_per_hour(30.0), speed: Speed::miles_per_hour(30.0),
max_energy: 100, max_energy: 50,
draw_frames: vec!["bike1.svg", "bike2.svg", "bike1.svg", "bike3.svg"], draw_frames: vec!["bike1.svg", "bike2.svg", "bike1.svg", "bike3.svg"],
scale: 0.05, scale: 0.05,

View File

@ -123,7 +123,7 @@ impl GeomBatch {
} }
/// True when the batch is empty. /// True when the batch is empty.
pub(crate) fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.list.is_empty() self.list.is_empty()
} }