use bevy::{
prelude::*,
render::{
camera::RenderTarget,
render_resource::{
Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
},
texture::BevyDefault,
},
};
use bevy_rapier3d::prelude::*;
use rand::Rng;
const PLANE_SIZE: (f32, f32) = (64.0, 64.0);
pub struct DicePlugin;
#[derive(Default, Resource)]
pub struct DicePluginSettings {
pub render_size: (u32, u32),
pub number_of_fields: usize,
pub render_handles: Vec<Handle<Image>>,
pub start_position: Vec3,
pub dice_scale: f32,
}
impl Plugin for DicePlugin {
fn build(&self, app: &mut App) {
app.insert_resource(DiceRollResult::default())
.add_event::<DiceRollEndEvent>()
.add_event::<DiceRollStartEvent>()
.add_event::<DiceRollResult>()
.add_startup_system(setup_scene)
.add_system(event_collisions)
.add_system(event_stop_dice_rolls)
.add_system(event_start_dice_roll);
}
}
#[derive(Component)]
pub struct DiceCamera;
fn setup_scene(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut images: ResMut<Assets<Image>>,
mut plugin_settings: ResMut<DicePluginSettings>,
) {
let size = Extent3d {
width: plugin_settings.render_size.0,
height: plugin_settings.render_size.1,
..default()
};
let mesh = meshes.add(Mesh::from(shape::Plane {
size: PLANE_SIZE.0 * PLANE_SIZE.1,
..default()
}));
let material_handle = materials.add(Color::GREEN.into());
for _ in 0..plugin_settings.number_of_fields {
let mut image = Image {
texture_descriptor: TextureDescriptor {
label: None,
size,
dimension: TextureDimension::D2,
format: TextureFormat::bevy_default(),
mip_level_count: 1,
sample_count: 1,
usage: TextureUsages::TEXTURE_BINDING
| TextureUsages::COPY_DST
| TextureUsages::RENDER_ATTACHMENT,
view_formats: &[TextureFormat::Rgba8Unorm],
},
..default()
};
image.resize(size);
let image_handle = images.add(image);
plugin_settings.render_handles.push(image_handle.clone());
commands
.spawn(Camera3dBundle {
transform: Transform::from_translation(
plugin_settings.start_position + Vec3::new(1.0, 3.0, 1.0),
)
.looking_at(plugin_settings.start_position, Vec3::Y),
camera: Camera {
target: RenderTarget::Image(image_handle.clone()),
..default()
},
..default()
})
.insert(DiceCamera)
.insert(Name::new("Dice Camera"))
.insert(UiCameraConfig { show_ui: false });
commands
.spawn(PbrBundle {
mesh: mesh.clone(),
transform: Transform::from_translation(plugin_settings.start_position),
material: material_handle.clone(),
..Default::default()
})
.insert(RigidBody::Fixed)
.insert(Collider::cuboid(PLANE_SIZE.0, 1.0, PLANE_SIZE.1))
.insert(ActiveEvents::CONTACT_FORCE_EVENTS)
.insert(Name::new("Ground"));
}
}
#[derive(Component)]
struct Dice {
pub world: usize,
}
#[allow(clippy::too_many_arguments)]
fn event_start_dice_roll(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut events: EventReader<DiceRollStartEvent>,
q_dice: Query<(Entity, &Dice)>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut previous_roll: ResMut<DiceRollResult>,
plugin_settings: Res<DicePluginSettings>,
) {
if events.iter().len() == 0 {
return;
}
previous_roll.values = Vec::new();
q_dice.iter().for_each(|(entity, _)| {
commands.entity(entity).despawn_recursive();
});
let scene_handle = asset_server.load("models/dice/scene.gltf#Scene0");
let transparent_material_handle = materials.add(Color::rgba(0., 0., 0., 0.).into());
let mut rng = rand::thread_rng();
for event in events.iter() {
for i in 0..plugin_settings.number_of_fields {
for _ in 0..event.num_dice[i] {
let rotation = Quat::from_euler(
EulerRot::XYZ,
rng.gen_range(0.0..std::f32::consts::PI * 2.0),
rng.gen_range(0.0..std::f32::consts::PI * 2.0),
rng.gen_range(0.0..std::f32::consts::PI * 2.0),
);
let transform = Transform::from_translation(
plugin_settings.start_position + Vec3::new(0.0, rng.gen_range(4.0..7.0), 0.0),
)
.with_rotation(rotation)
.with_scale(Vec3::splat(plugin_settings.dice_scale));
commands
.spawn(())
.insert(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.10 })),
transform,
material: transparent_material_handle.clone(),
..default()
})
.with_children(|parent| {
parent.spawn(SceneBundle {
scene: scene_handle.clone(),
transform: Transform::from_xyz(0., 0.0, 0.)
.with_scale(Vec3::splat(0.1)),
..default()
});
})
.insert(Name::new("Dice"))
.insert(RigidBody::Dynamic)
.insert(Collider::cuboid(0.05, 0.05, 0.05))
.insert(ActiveEvents::COLLISION_EVENTS)
.insert(Dice { world: i });
}
}
}
}
fn event_collisions(
mut collision_events: EventReader<CollisionEvent>,
mut contact_force_events: EventReader<ContactForceEvent>,
mut ev_dice_stopped: EventWriter<DiceRollEndEvent>,
q_dice: Query<(Entity, &Dice)>,
) {
let is_dice = |entity: &Entity| q_dice.get(*entity).is_ok();
for event in contact_force_events.iter() {
if is_dice(&event.collider1) {
ev_dice_stopped.send(DiceRollEndEvent(event.collider1));
}
if is_dice(&event.collider2) {
ev_dice_stopped.send(DiceRollEndEvent(event.collider2));
}
}
for event in collision_events.iter() {
match event {
CollisionEvent::Started(entity_1, entity_2, _) => {
if is_dice(entity_1) {
ev_dice_stopped.send(DiceRollEndEvent(*entity_1));
}
if is_dice(entity_2) {
ev_dice_stopped.send(DiceRollEndEvent(*entity_2));
}
}
CollisionEvent::Stopped(entity_1, entity_2, _) => {
if is_dice(entity_1) {
ev_dice_stopped.send(DiceRollEndEvent(*entity_1));
}
if is_dice(entity_2) {
ev_dice_stopped.send(DiceRollEndEvent(*entity_2));
}
}
}
}
}
struct DiceRollEndEvent(Entity);
pub struct DiceRollStartEvent {
pub num_dice: Vec<usize>,
}
const CUBE_SIDES: [Vec3; 6] = [
Vec3::new(0.0, 1.0, 0.0),
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(0.0, 0.0, -1.0),
Vec3::new(0.0, 0.0, 1.0),
Vec3::new(-1.0, 0.0, 0.0),
Vec3::new(0.0, -1.0, 0.0),
];
#[derive(Default, Clone, Resource)]
pub struct DiceRollResult {
pub values: Vec<Vec<usize>>,
}
fn event_stop_dice_rolls(
mut event_reader: EventReader<DiceRollEndEvent>,
mut event_writer: EventWriter<DiceRollResult>,
query: Query<(Entity, &Transform, &Dice)>,
mut previous_roll: ResMut<DiceRollResult>,
plugin_settings: Res<DicePluginSettings>,
) {
for _ in event_reader.iter() {
let mut result = DiceRollResult { ..default() };
for _ in 0..plugin_settings.number_of_fields {
result.values.push(Vec::new());
}
for (_, (_, transform, dice)) in query.iter().enumerate() {
let mut height = 0.0;
let mut value = 0;
for (i, side) in CUBE_SIDES.iter().enumerate() {
let y = transform.rotation.mul_vec3(*side)[1];
if height < y {
height = y;
value = i + 1;
}
}
result.values[dice.world].push(value);
}
if previous_roll.values != result.values {
previous_roll.values = result.values.clone();
event_writer.send(result.clone());
}
}
}