#[cfg(feature = "client")]
use let_engine::prelude::*;
#[cfg(feature = "client")]
use std::{
f64::consts::{FRAC_PI_2, FRAC_PI_4},
sync::Arc,
time::{Duration, SystemTime},
};
#[cfg(feature = "client")]
const RESOLUTION: Vec2 = vec2(800.0, 600.0);
#[cfg(not(feature = "client"))]
fn main() {
eprintln!("This example requires you to have the `client` feature enabled.");
}
#[cfg(feature = "client")]
fn main() {
let window_builder = WindowBuilder::default()
.resizable(false)
.inner_size(RESOLUTION)
.title("Pong 2");
let engine = Engine::new(
EngineSettingsBuilder::default()
.window_settings(window_builder)
.tick_settings(
TickSettingsBuilder::default()
.update_physics(false)
.tick_wait(Duration::from_secs_f64(1.0 / 20.0)) .build()
.unwrap(),
)
.build()
.unwrap(),
)
.unwrap();
let game = Game::new();
engine.start(game);
}
#[cfg(feature = "client")]
struct Game {
exit: bool,
left_paddle: Paddle,
right_paddle: Paddle,
ball: Ball,
left_score: Label<Object>,
right_score: Label<Object>,
}
#[cfg(feature = "client")]
impl Game {
pub fn new() -> Self {
let game_layer = SCENE.new_layer();
let ui_layer = SCENE.new_layer();
game_layer.set_camera_settings(CameraSettings::default().mode(CameraScaling::Limited));
ui_layer.set_camera_settings(
CameraSettings::default()
.mode(CameraScaling::Expand)
.zoom(0.8),
);
let left_paddle = Paddle::new(&game_layer, (VirtualKeyCode::W, VirtualKeyCode::S), -0.95);
let right_paddle = Paddle::new(&game_layer, (VirtualKeyCode::K, VirtualKeyCode::J), 0.95);
let ball = Ball::new(&game_layer);
let font = Font::from_slice(include_bytes!("Px437_CL_Stingray_8x16.ttf"))
.expect("Font is invalid.");
let left_score = Label::new(
&font,
LabelCreateInfo {
appearance: Appearance::new().transform(Transform::default().size(vec2(0.5, 0.7))),
text: "0".to_string(),
align: Direction::No,
transform: Transform::default().position(vec2(-0.55, 0.0)),
scale: vec2(50.0, 50.0),
},
);
let left_score = left_score.init(&ui_layer).unwrap();
let right_score = Label::new(
&font,
LabelCreateInfo {
appearance: Appearance::new().transform(Transform::default().size(vec2(0.5, 0.7))),
text: "0".to_string(),
align: Direction::Nw,
transform: Transform::default().position(vec2(0.55, 0.0)),
scale: vec2(50.0, 50.0),
},
);
let right_score = right_score.init(&ui_layer).unwrap();
let mut middle_line = NewObject::new();
const MIDDLE_DATA: Data = Data::new_fixed(
&[
vert(0.0, 0.7),
vert(0.0, 0.3),
vert(0.0, -0.3),
vert(0.0, -0.7),
],
&[0, 1, 2, 3],
);
middle_line
.appearance
.set_model(Model::Custom(ModelData::new(MIDDLE_DATA).unwrap()));
let line_material = MaterialSettingsBuilder::default()
.line_width(10.0)
.topology(Topology::LineList)
.build()
.unwrap();
let line_material = Material::new(line_material).unwrap();
middle_line.appearance.set_material(Some(line_material));
middle_line.init(&ui_layer).unwrap();
Self {
exit: false,
left_paddle,
right_paddle,
ball,
left_score,
right_score,
}
}
}
#[cfg(feature = "client")]
impl let_engine::Game for Game {
fn update(&mut self) {
self.left_paddle.update();
self.right_paddle.update();
self.ball.update();
self.left_score
.update_text(format!("{}", self.ball.wins[0]));
self.right_score
.update_text(format!("{}", self.ball.wins[1]));
}
fn event(&mut self, event: events::Event) {
match event {
Event::Window(WindowEvent::CloseRequested) => {
self.exit = true;
}
Event::Input(InputEvent::KeyboardInput { input }) => {
if input.state == ElementState::Pressed {
match input.keycode {
Some(VirtualKeyCode::Escape) => self.exit = true,
Some(VirtualKeyCode::E) => {
self.right_paddle.shrink();
}
Some(VirtualKeyCode::Q) => {
self.left_paddle.grow();
}
Some(VirtualKeyCode::Left) => {
self.left_paddle.shrink();
}
Some(VirtualKeyCode::Right) => {
self.right_paddle.grow();
}
_ => (),
}
}
}
_ => (),
}
}
fn exit(&self) -> bool {
self.exit
}
}
#[cfg(feature = "client")]
struct Paddle {
controls: (VirtualKeyCode, VirtualKeyCode), object: Object,
height: f32,
}
#[cfg(feature = "client")]
impl Paddle {
pub fn new(layer: &Arc<Layer>, controls: (VirtualKeyCode, VirtualKeyCode), x: f32) -> Self {
let height = 0.05;
let mut object = NewObject::new();
object.transform = Transform {
position: vec2(x, 0.0),
size: vec2(0.015, height),
..Default::default()
};
object.set_collider(Some(ColliderBuilder::square(0.015, height).build()));
let object = object.init(layer).unwrap();
Self {
controls,
object,
height,
}
}
pub fn update(&mut self) {
let shift = INPUT.key_down(self.controls.0) as i32 - INPUT.key_down(self.controls.1) as i32;
let y = &mut self.object.transform.position.y;
*y -= shift as f32 * TIME.delta_time() as f32 * 1.3;
*y = y.clamp(-0.70, 0.70);
self.object.sync();
}
pub fn shrink(&mut self) {
self.resize(-0.001);
}
pub fn grow(&mut self) {
self.resize(0.001);
}
fn resize(&mut self, difference: f32) {
self.height += difference;
self.height = self.height.clamp(0.001, 0.7);
self.object.transform.size.y = self.height;
self.object
.set_collider(Some(ColliderBuilder::square(0.015, self.height).build()));
self.object.sync();
}
}
#[cfg(feature = "client")]
struct Ball {
object: Object,
layer: Arc<Layer>,
direction: Vec2,
speed: f32,
new_round: SystemTime,
pub wins: [u32; 2],
bounce_sound: Sound,
}
#[cfg(feature = "client")]
impl Ball {
pub fn new(layer: &Arc<Layer>) -> Self {
let lifetime = SystemTime::now();
let mut object = NewObject::new();
object.transform.size = vec2(0.015, 0.015);
let object = object.init(layer).unwrap();
let mut bounce_sound = Sound::new(
SoundData::gen_square_wave(777.0, 0.03),
SoundSettings::default().volume(0.05),
);
bounce_sound.bind_to_object(Some(&object));
Self {
object,
layer: layer.clone(),
direction: Self::random_direction(),
speed: 1.1,
new_round: lifetime,
wins: [0; 2],
bounce_sound,
}
}
pub fn update(&mut self) {
if self.new_round.elapsed().unwrap().as_secs() > 0 {
let position = self.object.transform.position;
let touching_paddle = self
.layer
.intersection_with_shape(Shape::square(0.02, 0.02), (position, 0.0))
.is_some();
let touching_floor =
position.y < self.layer.side_to_world(vec2(0.0, 1.0), RESOLUTION).y + 0.015;
let touching_roof =
position.y > self.layer.side_to_world(vec2(0.0, -1.0), RESOLUTION).y - 0.015;
let touching_wall = position.x.abs() > 1.0;
if touching_paddle
&& (self.direction.x.is_sign_negative()
== self.object.transform.position.x.is_sign_negative())
{
self.rebound(position.x as f64);
self.speed += 0.02;
} else if touching_floor {
self.direction.y = self.direction.y.abs();
} else if touching_roof {
self.direction.y = -self.direction.y.abs();
} else if touching_wall {
if position.x.is_sign_negative() {
self.wins[1] += 1;
} else {
self.wins[0] += 1;
}
self.reset();
return;
}
self.object.transform.position +=
self.direction * TIME.delta_time() as f32 * self.speed;
self.object.sync();
self.bounce_sound.update(Tween::default()).unwrap();
}
}
fn reset(&mut self) {
self.new_round = SystemTime::now();
self.object.transform.position = vec2(0.0, 0.0);
self.direction = Self::random_direction();
self.speed = 1.1;
self.object.sync();
}
fn rebound(&mut self, x: f64) {
let random = (TIME.time() * 135225.3).sin().copysign(-x);
let direction = random.mul_add(FRAC_PI_2, FRAC_PI_4.copysign(-x)) - FRAC_PI_2;
self.direction = Vec2::from_angle(direction as f32).normalize();
self.bounce_sound.play().unwrap();
}
fn random_direction() -> Vec2 {
let random = (TIME.time() * 135225.3).sin();
let direction = random.mul_add(FRAC_PI_2, FRAC_PI_4.copysign(random)) - FRAC_PI_2;
Vec2::from_angle(direction as f32)
}
}